diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 00000000000..8061d523e5c --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,3 @@ +ignore: + - "src/main/java/seedu/address/ui" + - "src/main/java/seedu/address/model/util/SampleDataUtil.java" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000000..1e1862b0e82 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,25 @@ +name: MarkBind Action + +on: + push: + branches: + - master + +jobs: + build_and_deploy: + runs-on: ubuntu-latest + steps: + - name: Install Graphviz + run: sudo apt-get install graphviz + - name: Install Java + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'temurin' + - name: Build & Deploy MarkBind site + uses: MarkBind/markbind-action@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + rootDirectory: './docs' + baseUrl: '/tp' # replace with your repo name + version: '^5.1.0' diff --git a/.gitignore b/.gitignore index 284c4ca7cd9..1308aecbe96 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ src/test/data/sandbox/ # MacOS custom attributes files created by Finder .DS_Store docs/_site/ +docs/_markbind/logs/ + +/bin/ diff --git a/README.md b/README.md index 13f5c77403f..9b572a3183e 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,12 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) - +[![CI Status](https://github.com/AY2324S1-CS2103T-T12-4/tp/actions/workflows/gradle.yml/badge.svg)](https://github.com/AY2324S1-CS2103T-T12-4/tp/actions) +[![codecov](https://codecov.io/gh/AY2324S1-CS2103T-T12-4/tp/graph/badge.svg?token=XRD2EIUJ8H)](https://codecov.io/gh/AY2324S1-CS2103T-T12-4/tp) ![Ui](docs/images/Ui.png) -* This is **a sample project for Software Engineering (SE) students**.
- Example usages: - * as a starting point of a course project (as opposed to writing everything from scratch) - * as a case study -* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details. - * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big. - * It comes with a **reasonable level of user and developer documentation**. -* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...). -* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**. -* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info. +- This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). + +- It is named MedBook, a brownfield project from the AddressBook-Level3. +- It can: + - streamline patient management for healthcare professionals + - simplify the process of accessing patients' contact information and medical records +- To access the User Guide, click [here](/docs/UserGuide.md) +- To access the Developer Guide, click [here](/docs/DeveloperGuide.md) diff --git a/build.gradle b/build.gradle index a2951cc709e..b0ac5e57864 100644 --- a/build.gradle +++ b/build.gradle @@ -66,7 +66,11 @@ dependencies { } shadowJar { - archiveFileName = 'addressbook.jar' + archiveFileName = 'medbook.jar' +} + +run { + enableAssertions = true } defaultTasks 'clean', 'test' diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000000..1748e487fbd --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,23 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +_markbind/logs/ + +# Dependency directories +node_modules/ + +# Production build files (change if you output the build to a different directory) +_site/ + +# Env +.env +.env.local + +# IDE configs +.vscode/ +.idea/* +*.iml diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..0336f94d7ff 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,59 +1,57 @@ --- -layout: page -title: About Us + layout: default.md + title: "About Us" --- +# About Us + We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg). -You can reach us at the email `seer[at]comp.nus.edu.sg` +You can reach us at the email `low.darren[at]u.nus.edu` ## Project team -### John Doe +### Darren Low - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/Darren159)] +[[portfolio](team/darren159.md)] -* Role: Project Advisor +- Role: Developer -### Jane Doe +### Kim Hanjoo - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/hjoneweek)] +[[portfolio](team/hjoneweek.md)] -* Role: Team Lead -* Responsibilities: UI +- Role: Developer -### Johnny Doe +### Adam Ang Zi Jun - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](https://github.com/adammangzijun)] +[[portfolio](team/adammangzijun.md)] -* Role: Developer -* Responsibilities: Data +- Role: Developer -### Jean Doe +### Lin Yuxiang - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/Clin-lyx)] +[[portfolio](team/clin-lyx.md)] -* Role: Developer -* Responsibilities: Dev Ops + Threading +- Role: Developer -### James Doe +### Ryan Ong Wei Xian - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/ryanongwx)] +[[portfolio](team/ryanongwx.md)] -* Role: Developer -* Responsibilities: UI +- Role: Developer diff --git a/docs/Configuration.md b/docs/Configuration.md index 13cf0faea16..32f6255f3b9 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -1,6 +1,8 @@ --- -layout: page -title: Configuration guide + layout: default.md + title: "Configuration guide" --- +# Configuration guide + Certain properties of the application can be controlled (e.g user preferences file location, logging level) through the configuration file (default: `config.json`). diff --git a/docs/DevOps.md b/docs/DevOps.md index d2fd91a6001..736da195dc2 100644 --- a/docs/DevOps.md +++ b/docs/DevOps.md @@ -1,38 +1,39 @@ --- -layout: page -title: DevOps guide + layout: default.md + title: "DevOps guide" + pageNav: 3 --- -* Table of Contents -{:toc} +# DevOps guide --------------------------------------------------------------------------------------------------------------------- + + + + ## Build automation This project uses Gradle for **build automation and dependency management**. **You are recommended to read [this Gradle Tutorial from the se-edu/guides](https://se-education.org/guides/tutorials/gradle.html)**. - Given below are how to use Gradle for some important project tasks. - -* **`clean`**: Deletes the files created during the previous build tasks (e.g. files in the `build` folder).
+- **`clean`**: Deletes the files created during the previous build tasks (e.g. files in the `build` folder).
e.g. `./gradlew clean` -* **`shadowJar`**: Uses the ShadowJar plugin to creat a fat JAR file in the `build/lib` folder, *if the current file is outdated*.
+- **`shadowJar`**: Uses the ShadowJar plugin to creat a fat JAR file in the `build/lib` folder, _if the current file is outdated_.
e.g. `./gradlew shadowJar`. -* **`run`**: Builds and runs the application.
+- **`run`**: Builds and runs the application.
**`runShadow`**: Builds the application as a fat JAR, and then runs it. -* **`checkstyleMain`**: Runs the code style check for the main code base.
+- **`checkstyleMain`**: Runs the code style check for the main code base.
**`checkstyleTest`**: Runs the code style check for the test code base. -* **`test`**: Runs all tests. - * `./gradlew test` — Runs all tests - * `./gradlew clean test` — Cleans the project and runs tests +- **`test`**: Runs all tests. + - `./gradlew test` — Runs all tests + - `./gradlew clean test` — Cleans the project and runs tests --------------------------------------------------------------------------------------------------------------------- +--- ## Continuous integration (CI) @@ -58,22 +59,23 @@ Any warnings or errors will be printed out to the console. **If adding new checks:** -* Checks are implemented as executable `check-*` scripts within the `.github` directory. The `run-checks.sh` script will automatically pick up and run files named as such. That is, you can add more such files if you need and the CI will do the rest. +- Checks are implemented as executable `check-*` scripts within the `.github` directory. The `run-checks.sh` script will automatically pick up and run files named as such. That is, you can add more such files if you need and the CI will do the rest. + +- Check scripts should print out errors in the format `SEVERITY:FILENAME:LINE: MESSAGE` -* Check scripts should print out errors in the format `SEVERITY:FILENAME:LINE: MESSAGE` - * SEVERITY is either ERROR or WARN. - * FILENAME is the path to the file relative to the current directory. - * LINE is the line of the file where the error occurred and MESSAGE is the message explaining the error. + - SEVERITY is either ERROR or WARN. + - FILENAME is the path to the file relative to the current directory. + - LINE is the line of the file where the error occurred and MESSAGE is the message explaining the error. -* Check scripts must exit with a non-zero exit code if any errors occur. +- Check scripts must exit with a non-zero exit code if any errors occur. --------------------------------------------------------------------------------------------------------------------- +--- ## Making a release Here are the steps to create a new release. -1. Update the version number in [`MainApp.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java). +1. Update the version number in [`MainApp.java`](https://github.com/AY2324S1-CS2103T-T12-4/tp/blob/master/src/main/java/seedu/address/MainApp.java). 1. Generate a fat JAR file using Gradle (i.e., `gradlew shadowJar`). 1. Tag the repo with the version number. e.g. `v0.1` 1. [Create a new release using GitHub](https://help.github.com/articles/creating-releases/). Upload the JAR file you created. diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 8a861859bfd..f06619c0d1e 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,377 +1,1481 @@ --- -layout: page -title: Developer Guide +layout: default.md +title: "Developer Guide" +pageNav: 2 --- -* Table of Contents -{:toc} --------------------------------------------------------------------------------------------------------------------- +# Developer Guide -## **Acknowledgements** +## Acknowledgements -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +This project has not used any third-party libraries. --------------------------------------------------------------------------------------------------------------------- +--- -## **Setting up, getting started** +## Setting up and Getting Started -Refer to the guide [_Setting up and getting started_](SettingUp.md). +For initial setup and getting started with the development, please refer to the guide: [_Setting up and getting started_](SettingUp.md). --------------------------------------------------------------------------------------------------------------------- +--- -## **Design** +## Design Overview -
+### Architecture -:bulb: **Tip:** The `.puml` files used to create diagrams in this document `docs/diagrams` folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams. -
+ -### Architecture +The **_Architecture Diagram_** above provides a high-level design overview of the application. + +Below is a quick overview of the main components and their interactions: + +--- + +
+ +#### Main Components: + +- **`Main`**: Responsible for initializing other components in the correct sequence upon application launch, and ensures proper shut down procedures are followed. It consists of [`Main`](https://github.com/AY2324S1-CS2103T-T12-4/tp/blob/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/AY2324S1-CS2103T-T12-4/tp/blob/master/src/main/java/seedu/address/MainApp.java) classes. + +- **`UI`** ([Details](#ui-component)): Manages the User Interface of the app. +- **`Logic`** ([Details](#logic-component)): Handles command execution. +- **`Model`** ([Details](#model-component)): Manages in-memory data. +- **`Storage`** ([Details](#storage-component)): Handles reading from and writing to the hard disk. + +- **`Commons`**: A collection of classes used by multiple components. + +#### Component Interactions: + +The sequence diagram below shows the interactions between components for the `delete 1` command: + + + +Each of the four main components: + +- Defines its API in an interface named after the component. +- Implements its functionality using a `{Component Name}Manager` class, following the corresponding API interface. + +For example, the `Logic` component's API is defined in `Logic.java`, and its functionality is implemented in `LogicManager.java`. + +--- + +### Detailed Component Descriptions + +#### UI Component + +- **API**: [`Ui.java`](https://github.com/AY2324S1-CS2103T-T12-4/tp/blob/master/src/main/java/seedu/address/ui/Ui.java) +- The `UI` component is responsible for handling all user interface operations. + +- Core `UI` Components: + + +- Supplementary `UI` Components: + + +The `UI` is composed of various components such as `MainWindow`, `CommandBox`, `ResultDisplay`, `PersonListPanel`, and `StatusBarFooter`, all of which inherit from the `UiPart` class. + +The `UI` layouts are defined in corresponding `.fxml` files located in the `src/main/resources/view` folder. + +Key responsibilities include: + +- Executing user commands via the `Logic` component. +- Listening for data changes in the `Model` and updating the `UI` accordingly. +- Maintaining a reference to the `Logic` component for command execution. + +--- + +
+ +#### Logic Component + +- **API**: [`Logic.java`](https://github.com/AY2324S1-CS2103T-T12-4/tp/blob/master/src/main/java/seedu/address/logic/Logic.java) +- The Logic component is responsible for interpreting and executing user commands. + +Here's a partial class diagram of the `Logic` component: + + + +Key functionalities include: + +- Interpreting user input and generating the corresponding `Command` object. +- Executing the command and generating a `CommandResult` object. +- Depending on the `Model` component to perform data operations. +- Managing various command parsers to handle specific command formats. + +--- + +
+ +#### Model Component + +- **API**: [`Model.java`](https://github.com/AY2324S1-CS2103T-T12-4/tp/blob/master/src/main/java/seedu/address/model/Model.java) +- The Model component manages the application's in-memory data. + + + +Key responsibilities include: + +- Storing address book data. +- Managing user preferences. +- Providing an unmodifiable view of lists of `Person`, `Record` and `Appointment` objects for UI binding. + +--- + +
+ +#### Storage Component + +- **API**: [`Storage.java`](https://github.com/AY2324S1-CS2103T-T12-4/tp/blob/master/src/main/java/seedu/address/storage/Storage.java) +- The Storage component manages data persistence. + + + +Key functionalities include: + +- Saving and retrieving address book data and user preferences in JSON format. +- Implementing both `AddressBookStorage` and `UserPrefStorage` for flexibility. +- Depending on certain `Model` classes for data object definitions. + +--- + +## Common Classes + +Classes used by multiple components are housed in the `seedu.addressbook.commons` package. + +--- + +
+ +## Patient Features + +### General Implementation Details + + + +A `Person` object encapsulates various attributes: + +- `Name`: Patient's first and last name (and middle name, if applicable) +- `Nric`: Patient's NRIC +- `Email`: Patient's email address +- `Phone`: Patient's phone number +- `Gender`: Patient's gender +- `Age`: Patient's age +- `BloodType`: Patient's blood type +- `Set`: Patient's allergies +- `UniqueRecordList`: Patient's records from past visits to the clinic. +- `UniqueAppointmentList`: Patient's scheduled appointments. +- `isPinned`: Patient's pin status. + +Uniqueness of person is maintained through the `UniquePersonList`. + +--- + +### Adding a Patient + +#### Overview + +The `addpatient` command integrates a new `Person` object with the patient's details in MedBook. + +#### Related Classes and Methods + +- `AddCommandParser#parse(String)` +- `AddCommand#execute(Model)` +- `Model#addPerson(Person)`, `AddressBook#addPerson(Person)`, `UniquePersonList#add(Person)` +- `PersonListPanel`, `PersonCard` + +#### Implementation Steps + +1. **Parse User Input**: `AddCommandParser` checks for necessary parameters and their validity. +2. **Create Person Object**: A `Person` object is instantiated during `AddCommandParser#parse(String)` and passed over to the `AddCommand`. +3. **Execute Command**: `AddPersonCommand#execute(Model)` adds the new `Person` to the `UniquePersonList` in the `Model`. + + + +--- + +### Editing a Patient's Details + +#### Overview + +The `editpatient` command facilitates the modification of patient information by updating the fields of a `Person` object with new details. + +#### Related Classes and Methods + +- `EditCommandParser#parse(String)` +- `EditPersonDescriptor` +- `EditCommand#execute(Model)` +- `ModelManager#setPerson(Person,Person)`, `AddressBook#setPerson(Person,Person)`, `UniquePersonList#setPerson(Person,Person)` +- `PersonListPanel`, `PersonCard` + +#### Implementation Steps + +1. **Parse User Input**: `EditCommandParser` checks for necessary parameters and their validity. +2. **Create Person Object**: A `Person` object with the edited details is instantiated during `EditCommandParser#parse(String)` and passed over to the `EditCommand`. +3. **Execute Command**: `EditCommand#execute(Model)` replaces the `Person` object in `Model` with a new `Person` object that has edited details. + + + +#### Design Considerations + +**Alternative 1 (Current Choice)**: Implement an Edit-by-Cloning Strategy + +- _Pros_: + - **Scalability:** By cloning the `Person` object before editing, the system is better equipped to handle future enhancements that may require complex transactional operations. + - **Data Integrity:** This method ensures that the original `Person` object remains unaltered during the edit process, which reduces the risk of data corruption in the event of an operation failure. +- _Cons_: + - Adds complexity and has potential performance issues. + +**Alternative 2**: Modify the `Person` object directly + +- _Pros_: + - **Simplicity:** This straightforward approach requires less code, making it easier to implement and understand. + - **Efficiency:** Operating directly on the `Person` object without cloning can be more performant, especially when dealing with simple edits that do not span multiple data fields. +- _Cons_: + - **Limited Flexibility:** Direct modification constrains the ability to extend the system with complex transactional features or undo/redo capabilities without significant refactoring. + - **Data Risk:** Without the safeguard of working on a cloned instance, there's a higher risk of inadvertently corrupting data during edit operations. + +By considering these alternatives, the development team has chosen to prioritize a robust foundation for future development and data integrity, despite the trade-offs in complexity and potential impact on performance. + +--- + +### Deleting a Patient + +#### Overview + +The `delete` command deletes an existing `Person` object from MedBook. + +#### Related Classes and Methods + +- `DeleteCommandParser#parse(String)` +- `DeleteCommand#execute(Model)` +- `ModelManager#deletePerson(Person)`, `AddressBook#removePerson(Person)`, `UniquePersonList#remove(Person)` +- `PersonListPanel`, `PersonCard` + +#### Implementation Steps - +1. **Parse User Input**: `DeleteCommandParser` checks for the validity of the `Person` index. +2. **Create Person Object**: An `Index` object of the `Person` is instantiated during `DeleteCommandParser#parse(String)` and passed over to the `DeleteCommand`. +3. **Execute Command**: `DeleteCommand#execute(Model)` deletes the `Person` object from `Model`. -The ***Architecture Diagram*** given above explains the high-level design of the App. + -Given below is a quick overview of main components and how they interact with each other. +--- + +### Searching for Patients + +#### Overview + +The `search` command filters the list of patients using one or more keywords. + +#### Related Classes and Methods + +- `FindCommandParser#parse(String)` +- `FindCommand#execute(Model)` +- `Model#updateFilteredList(Predicate)` +- `NameContainsKeywordsPredicate#test(Person)` +- `PersonListPanel`, `PersonCard` + +#### Implementation Steps + +1. **Parse User Input**: `FindCommandParser` checks for existence of the keyword(s) and creates an array of keywords. +2. **Create Predicate Object**: A `NameContainsKeywordsPredicate` object is instantiated during `FindCommandParser#parse(String)` and passed over to the `FindCommand`. +3. **Execute Command**: `FindCommand#execute(Model)` finds patients containing keywords using `NameContainsKeywordsPredicate#test(Record)` and updates `FilteredPersonList`. + + + +--- + +### Pinning a Patient + +#### Overview -**Main components of the architecture** +The `pin` command pins a patient to the **Pinned Patient List** -**`Main`** (consisting of classes [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java)) is in charge of the app launch and shut down. -* At app launch, it initializes the other components in the correct sequence, and connects them up with each other. -* At shut down, it shuts down the other components and invokes cleanup methods where necessary. +#### Related Classes and Methods -The bulk of the app's work is done by the following four components: +- `PinCommandParser#parse(String)` +- `PinCommand#execute(Model)` +- `Model#setPerson(Person, Person)`, `AddressBook#setPerson(Person, Person)`, `UniquePersonList#setPerson(Person, Person)` +- `PinnedPersonListPanel`, `PersonCard` -* [**`UI`**](#ui-component): The UI of the App. -* [**`Logic`**](#logic-component): The command executor. -* [**`Model`**](#model-component): Holds the data of the App in memory. -* [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk. +#### Implementation Steps -[**`Commons`**](#common-classes) represents a collection of classes used by multiple other components. +1. **Parse User Input**: `PinCommandParser` checks for the validity of the `Person` index. +2. **Create Index Object**: An `Index` object of the `Person` is instantiated during `PinCommandParser#parse(String)` and passed over to the `PinCommand`. +3. **Execute Command**: `PinCommand#execute(Model)` replaces the `Person` object in `Model` with a new `Person` object that has `isPinned` set to `true`. -**How the architecture components interact with each other** + -The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. +--- - +### Unpinning a Patient -Each of the four main components (also shown in the diagram above), +#### Overview -* defines its *API* in an `interface` with the same name as the Component. -* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point. +The `unpin` command unpins a patient from the **Pinned Patient List** -For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below. +#### Related Classes and Methods - +- `UnpinCommandParser#parse(String)` +- `UnpinCommand#execute(Model)` +- `Model#setPerson(Person, Person)`, `AddressBook#setPerson(Person, Person)`, `UniquePersonList#setPerson(Person, Person)` +- `PinnedPersonListPanel`, `PersonCard` -The sections below give more details of each component. +#### Implementation Steps -### UI component +1. **Parse User Input**: `UnpinCommandParser` checks for the validity of the pinned `Person` index. +2. **Create Index Object**: An `Index` object is instantiated during `UnpinCommandParser#parse(String)` and passed over to the `UnpinCommand`. +3. **Execute Command**: `UnpinCommand#execute(Model)` replaces the `Person` object in `Model` with a new `Person` object that has `isPinned` set to `false`. -The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java) + -![Structure of the UI Component](images/UiClassDiagram.png) +--- -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. +
-The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml) +## Records Feature -The `UI` component, +### General Implementation Details -* executes user commands using the `Logic` component. -* listens for changes to `Model` data so that the UI can be updated with the modified data. -* keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands. -* depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`. + -### Logic component +A `Record` object encapsulates various attributes: -**API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java) +- `DateTime`: Date and time of the patient's clinic visit +- `List`: Patient's health conditions during the visit +- `List`: Medications prescribed to the patient -Here's a (partial) class diagram of the `Logic` component: +Uniqueness of record is maintained through the `UniqueRecordList`. - +--- -The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete 1")` API call as an example. +### Adding a Record -![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) +#### Overview -
:information_source: **Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. -
+The `addrecord` command integrates a new `Record` object with the record's details in MedBook. -How the `Logic` component works: +#### Related Classes and Methods -1. When `Logic` is called upon to execute a command, it is passed to an `AddressBookParser` object which in turn creates a parser that matches the command (e.g., `DeleteCommandParser`) and uses it to parse the command. -1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `DeleteCommand`) which is executed by the `LogicManager`. -1. The command can communicate with the `Model` when it is executed (e.g. to delete a person). -1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. +- `AddRecordCommandParser#parse(String)` +- `AddRecordCommand#execute(Model)` +- `UniqueRecordList#add(Record)` +- `Model#setPerson(Person, Person)`, `AddressBook#setPerson(Person, Person)`, `UniquePersonList#setPerson(Person, Person)` +- `Model#updateRecordList(Person)`, `AddressBook#setRecords(Person)`, `UniqueRecordList#setRecords(UniqueRecordList)` +- `RecordListPanel`, `RecordCard` -Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command: +#### Implementation Steps - +1. **Parse User Input**: `AddRecordCommandParser` checks for necessary parameters and their validity. +2. **Create Record Object**: A `Record` object is instantiated during `AddRecordCommandParser#parse(String)` and passed over to the `AddRecordCommand`. +3. **Execute Command**: `AddRecordCommand#execute(Model)` replaces the `Person` object in `Model` with a new `Person` object that has the added `Record`. `Model#updateRecordList(Person)` replaces the `UniqueRecordList` in `Model` with the `UniqueRecordList` of the new `Person` object. -How the parsing works: -* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object. -* All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. + -### Model component -**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java) +--- - +### Editing a Record's Details +#### Overview -The `Model` component, +The `editpatient` command facilitates the modification of record information by updating the fields of a `Record` object with new details. -* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object). -* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. -* stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects. -* does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components) +#### Related Classes and Methods -
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
+- `EditRecordCommandParser#parse(String)` +- `EditRecordDescriptor` +- `EditRecordCommand#execute(Model)` +- `ModelManager#setPerson(Person,Person)`, `AddressBook#setPerson(Person,Person)`, `UniquePersonList#setPerson(Person,Person)` +- `Model#updateRecordList(Person)`, `AddressBook#setRecords(Person)`, `UniqueRecordList#setRecords(UniqueRecordList)`, `UniqueRecordList#setRecord(Record)` +- `RecordListPanel`, `RecordCard` - +#### Implementation Steps -
+1. **Parse User Input**: `EditRecordCommandParser` checks for necessary parameters and their validity. +2. **Create Record Object**: A `Record` object with the edited details is instantiated during `EditRecordCommandParser#parse(String)` and passed over to the `EditRecordCommand`. +3. **Execute Command**: `EditRecordCommand#execute(Model)` replaces the `Person` object in `Model` with a new `Person` object that has the new edited `Record`. `Model#updateRecordList(Person)` replaces the `UniqueRecordList` in `Model` with the `UniqueRecordList` of the new `Person` object. + -### Storage component +#### Design Considerations -**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java) +Similar to editing patient, a clone is created and modified, and then replaces the original. - +**Alternative 1 (Current Choice):** Clone the `Record` object, modify the clone, and then replace the original. -The `Storage` component, -* can save both address book data and user preference data in JSON format, and read them back into corresponding objects. -* inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed). -* depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`) +- _Pros_: -### Common classes + - **Data Integrity:** By working on a clone, we minimize the risk of corrupting the original data in case of an error during the update process. + - **Undo/Redo Capability:** This approach allows for an easier implementation of undo/redo functionalities as we have distinct before and after states. + - **Consistency:** It maintains a consistent methodology with the editpatient command, which uses a similar approach for updating patient details. -Classes used by multiple components are in the `seedu.addressbook.commons` package. +- _Cons_: + - **Performance Overhead:** Cloning objects can introduce a performance hit, especially if the record is large or if there are many fields to update. + - **Complexity:** The codebase complexity increases due to the additional steps required to manage the cloning and replacement process. --------------------------------------------------------------------------------------------------------------------- +**Alternative 2:** Update the `Record` object directly. -## **Implementation** +- _Pros_: + - **Performance:** This approach is more performant since it involves direct manipulation of the object without the need to create a copy. + - **Simplicity:** The logic is more straightforward, as it doesn't involve cloning, making the code easier to understand and maintain. +- _Cons_: + - **Risk to Data Integrity:** Any errors during the update can corrupt the original data, as changes are made in place. + - **Difficulty in Extending Functionality:** Future features such as undo/redo or real-time collaboration could be harder to implement as changes are not isolated. -This section describes some noteworthy details on how certain features are implemented. +In conclusion, the decision to proceed with Alternative 1 was made to prioritize the application's long-term robustness and maintainability, accepting the trade-offs in performance and immediate simplicity for the sake of a safer and more extensible editing feature. -### \[Proposed\] Undo/redo feature +--- -#### Proposed Implementation +### Deleting a Record -The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations: +#### Overview -* `VersionedAddressBook#commit()` — Saves the current address book state in its history. -* `VersionedAddressBook#undo()` — Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` — Restores a previously undone address book state from its history. +The `deleterecord` command deletes an existing `Record` object from MedBook. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +#### Related Classes and Methods -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +- `DeleteRecordCommandParser#parse(String)` +- `DeleteRecordCommand#execute(Model)` +- `UniqueRecordList#remove(Record)` +- `Model#setPerson(Person, Person)`, `AddressBook#setPerson(Person, Person)`, `UniquePersonList#setPerson(Person, Person)` +- `Model#updateRecordList(Person)`, `AddressBook#setRecords(Person)`, `UniqueRecordList#setRecords(UniqueRecordList)` +- `RecordListPanel`, `RecordCard` -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +##### Implementation Steps -![UndoRedoState0](images/UndoRedoState0.png) +1. **Parse User Input**: `DeleteRecordCommandParser` checks for the validity of the patient and record indices. +2. **Create Index Object**: Two `Index` objects, patient index and record index, are instantiated during `DeleteRecordCommandParser#parse(String)` and passed over to the `DeleteRecordCommand`. +3. **Execute Command**: `DeleteRecordCommand#execute(Model)` replaces the `Person` object in `Model` with a new `Person` object that has the updated `UniqueRecordList`. `Model#updateRecordList(Person)` replaces the `UniqueRecordList` in `Model` with the `UniqueRecordList` of the new `Person` object. -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. + -![UndoRedoState1](images/UndoRedoState1.png) +--- -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +### Viewing a patient's medical records -![UndoRedoState2](images/UndoRedoState2.png) +The `view` command displays the list of records of the patient being viewed. -
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`. +#### Related Classes and Methods -
+- `ViewCommandParser#parse(String)` +- `ViewCommand#execute(Model)` +- `Model#updateRecordList(Person)`, `AddressBook#setRecords(Person)`, `UniqueRecordList#setRecords(UniqueRecordList)` +- `Model#getPersonBeingViewed()`, `AddressBook#getPersonBeingViewed()`, `Logic#getPersonBeingViewed()` +- `RecordListPanel`, `RecordCard` -Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +#### Implementation steps -![UndoRedoState3](images/UndoRedoState3.png) +1. **Parse User Input**: `ViewCommandParser` checks for validity of the patient index. +2. **Create Index Object**: An `Index` object is instantiated during `ViewCommandParser#parse(String)` and passed over to the `ViewCommand`. +3. **Update and Display**: `ViewCommand#execute(Model)` invokes the `Model#updateRecordList(Person)` to update the record list of the `Model`. The record list of the specific patient is displayed. -
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather -than attempting to perform the undo. + -
+--- -The following sequence diagram shows how the undo operation works: +### Searching for Records -![UndoSequenceDiagram](images/UndoSequenceDiagram.png) +#### Overview -
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +The `searchrecord` command filters the list of records of the patient being viewed using one or more keywords. -
+#### Related Classes and Methods -The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +- `FindRecordCommandParser#parse(String)` +- `FindRecordCommand#execute(Model)` +- `Model#updateFilteredRecordList(Predicate)` +- `RecordContainsKeywordsPredicate#test(Record)` +- `RecordListPanel`, `RecordCard` -
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. +#### Implementation Steps -
+1. **Parse User Input**: `FindRecordCommandParser` checks for existence of the keyword(s) and creates an array of keywords. +2. **Create Predicate Object**: A `RecordContainsKeywordsPredicate` object is instantiated during `FindRecordCommandParser#parse(String)` and passed over to the `FindRecordCommand`. +3. **Execute Command**: `FindRecordCommand#execute(Model)` finds records containing keywords using `RecordContainsKeywordsPredicate#test(Record)` and updates `FilteredRecordList`. -Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. + -![UndoRedoState4](images/UndoRedoState4.png) +--- + +### Attaching Files to Patient Records + +#### Overview -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow. +The feature allows users to associate files with patient records. -![UndoRedoState5](images/UndoRedoState5.png) +#### Related Class and Methods -The following activity diagram summarizes what happens when a user executes a new command: +- `RecordCard#handleAttachFile(ActionEvent)` +- `Record#setFilePath(FilePath, Index)` +- `EditRecordCommand#setFilePath(FilePath)` +- `StorageManager#saveAddressBook(Model)` +- `RecordCard#handleOpenFile(ActionEvent)` - +#### Implementation Steps -#### Design considerations: +1. **User Button Press**: `RecordCard#handleAttachFile(ActionEvent)` opens file explorer, prompting user to select the file to attach. +2. **Saving Filepath**: `EditRecordCommand#setFilePath(FilePath)` and `Record#setFilePath(FilePath, Index)` updates the filepath of the corresponding record. +3. **User File Press**: `RecordCard#handleOpenFile(ActionEvent)` opens the file. -**Aspect: How undo & redo executes:** +#### Alternative Considerations -* **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. +In designing this feature, we considered two primary approaches: -* **Alternative 2:** Individual command knows how to undo/redo by - itself. - * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). - * Cons: We must ensure that the implementation of each individual command are correct. +1. **Current Implementation (GUI)**: -_{more aspects and alternatives to be added}_ + - _Pros_: The graphical user interface (GUI) for file attachments is user-friendly, especially for individuals less familiar with command-line interfaces (CLI). + - _Cons_: It may be relatively slower for users proficient with CLI-based interactions. -### \[Proposed\] Data archiving +2. **Command-Line Interface (CLI)**: + - _Pros_: CLI-based file attachment would cater to users who prefer efficient, command-driven workflows. + - _Cons_: May require a learning curve for users less experienced with command-line interactions. + +Ultimately, the decision was made to implement the feature with a GUI to ensure accessibility and ease of use for a broader range of users, while still allowing for efficient management of patient records. + +--- -_{Explain here how the data archiving feature will be implemented}_ +
+## Appointments Feature --------------------------------------------------------------------------------------------------------------------- +### General Implementation Details -## **Documentation, logging, testing, configuration, dev-ops** + + +An `Appointment` object encapsulates various attributes: + +- `Name`: Appointment title. +- `DateTime`: Date and Time of the Appointment. +- `Nric`: Nric of the patient involved in the Appointment. + +Uniqueness is enforced through a `UniqueAppointmentList`. + +--- + +### Adding an Appointment + +#### Overview + +The `addappointment` command integrates a new `Appointment` object with the appointment's details in MedBook. + +#### Related Classes and Methods + +- `AddAppointmentCommandParser#parse(String)` +- `AddAppointmentCommand#execute(Model)` +- `UniqueAppointmentList#setAppointments(UniqueAppointmentList)`, `UniqueAppointmentList#add(Appointment)` +- `Model#setPerson(Person, Person)`, `AddressBook#setPerson(Person, Person)`, `UniquePersonList#setPerson(Person, Person)` +- `Model#resetAppointmentList()` +- `AppointmentWindow`, `AppointmentCalendarPanel`, `AppointmentListPanel`, `AppointmentCard` + +#### Implementation Steps + +1. **Parse User Input**: `AddAppointmentCommandParser` checks for necessary parameters and their validity. +2. **Create Appointment Object**: An `Appointment` object is instantiated during `AddAppointmentCommandParser#parse(String)` and passed over to the `AddAppointmentCommand`. +3. **Execute Command**: `AddAppointmentCommand#execute(Model)` replaces the `Person` object in `Model` with a new `Person` object that has the added `Appointment`. `Model#resetAppointmentList()` resets the `UniqueAppointmentList` of the `Model`. + + + +--- + +### Deleting an Appointment + +#### Overview + +The `deleteappointment` command deletes an existing `Appointment` from MedBook. + +#### Related Classes and Methods + +- `DeleteAppointmentCommandParser#parse(String)` +- `DeleteAppointmentCommand#execute(Model)` +- `UniqueAppointmentList#setAppointments(UniqueAppointmentList)`, `UniqueAppointmentList#remove(Appointment)` +- `Model#setPerson(Person, Person)`, `AddressBook#setPerson(Person, Person)`, `UniquePersonList#setPerson(Person, Person)` +- `Model#resetAppointmentList()` +- `AppointmentWindow`, `AppointmentCalendarPanel`, `AppointmentListPanel`, `AppointmentCard` + +#### Implementation Steps + +1. **Parse User Input**: `DeleteAppointmentCommandParser` checks for the validity of the appointment index. +2. **Create Index Object**: An `Index` object is instantiated during `DeleteAppointmentCommandParser#parse(String)` and passed over to the `DeleteAppointmentCommand`. +3. **Execute Command**: `DeleteAppointmentCommand#execute(Model)` replaces the `Person` object in `Model` with a new `Person` object that has the updated `UniqueAppointmentList`. `Model#resetAppointmentList()` resets the `UniqueAppointmentList` of the `Model`. + + + +--- -* [Documentation guide](Documentation.md) -* [Testing guide](Testing.md) -* [Logging guide](Logging.md) -* [Configuration guide](Configuration.md) -* [DevOps guide](DevOps.md) +### Viewing the Appointment Window --------------------------------------------------------------------------------------------------------------------- +#### Overview -## **Appendix: Requirements** +The `viewappointment` command opens/focuses the `AppointmentsWindow`. -### Product scope +#### Related Classes and Methods -**Target user profile**: +- `ViewAppointmentCommand#execute(Model)` +- `MainWindow#handleAppointments()`, `AppointmentsWindow#show()`, `AppointmentsWindow#fillInnerParts()` +- `AppointmentWindow`, `AppointmentCalendarPanel`, `AppointmentListPanel`, `AppointmentCard` -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing to mouse interactions -* is reasonably comfortable using CLI apps +#### Implementation Steps -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +1. **Execute Command**: `ViewAppointmentCommand#execute(Model)` returns a new `CommandResult` with `showAppointments` set to `true`. +2. **Show Appointments Window**: `MainWindow#handleAppointments()` calls the `AppointmentsWindow#show()` method which opens/focuses the `AppointmentsWindow`. +3. **Populate Appointments Data**: `AppointmentsWindow#fillInnerParts()` populates the `AppointmentCalendarPanel`, `AppointmentListPanel` with a `UniqueAppointmentList`. + -### User stories +### Design Considerations + +**Alternative 1 (Current Choice)**: Each `Person` consists of a `UniqueAppointmentList` consisting of all `Appointment` objects assigned to the `Person`. Each `Appointment` object has the corresponding `Person` `NRIC` as a field. + +- _Pros_: + + - **Direct Relationship**: It directly associates appointments with the individual patient, reflecting a real-world scenario where a patient has a list of their appointments. + - **Ease of Access**: Retrieving all appointments for a specific person is straightforward. + +- _Cons_: + - **Duplication**: Storing `NRIC` in both `Person` and `Appointment` leads to data duplication, going against the principles of data normalization. + - **Data Integrity Risks**: If an `NRIC` needs to be updated, it must be changed in each Appointment object, increasing the risk of data inconsistency. + - **Complexity**: It complicates the retrieval of information about the `Person` associated with the `Appointment` such as trying to display the `Person` `Name` in the **Appointment Card**. + +**Alternative 2**: Each `Appointment` consists of a a `Person` objects. Each `Person` object has the corresponding `Appointment` id as a field. + +- _Pros_: + + - **Data Normalization**: This approach avoids duplicating person-specific data (like `NRIC`) within the appointment, adhering to data normalization principles. + +- _Cons_: + + - **Complexity**: It complicates the retrieval of all appointments for a single person, requiring more complex queries and potentially impacting performance. + - **Data Redundancy**: If an appointments holds a `Person` object, there's a risk of redundancy as the same `Person` object could be stored across multiple appointments. + - **Difficult Person Creation**: The dependency of `Person` creation on `Appointment` objects complicates the process, potentially leading to issues when a new patient is registered without an appointment. + +Considering MedBook's primary focus on patient management, **Alternative 1** has been selected. This decision prioritizes the ease and intuitiveness of accessing and managing patient-specific information, despite some level of data redundancy, which is often a trade-off in software design for enhanced usability. + +--- + +
+ +## User Stories Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` -| Priority | As a …​ | I want to …​ | So that I can…​ | -| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- | -| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App | -| `* * *` | user | add a new person | | -| `* * *` | user | delete a person | remove entries that I no longer need | -| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list | -| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident | -| `*` | user with many persons in the address book | sort persons by name | locate a person easily | +| Priority | As a …​ | I can …​ | So that I can …​ | +| -------- | ------------- | ------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | +| `* * *` | user | _add_ a patient's detail including age, gender, or blood type etc. to the application | keep track of my patients | +| `* * *` | user | _add_ a patient’s medical records with details including date, time, condition and prescribed medication etc. | manage patients' medical records efficiently | +| `* * *` | user | _add_ a patient's appointments with details including date, time and title etc. with a patient | manage patients' appointments efficiently | +| `* * *` | user | _view_ a list of all the patients in the app | have an overview of all my patients | +| `* * *` | user | _view_ a list of all the medical records of a patient | have an overview of all the medical records of my patients | +| `* * *` | user | _view_ a list of all the upcoming appointments | have an overview of all my upcoming medical appointments | +| `* * *` | user | _edit_ an existing patient’s details | keep the patient's details accurate and up-to-date | +| `* * *` | user | _edit_ an existing medical record's details | keep the medical record's details accurate and up-to-date | +| `* * *` | user | _delete_ a specific patient from the app | remove patients that are no longer relevant or needed | +| `* * *` | user | _delete_ a specific medical record from the app | remove erroneous medical records. | +| `* * *` | user | _delete_ a specific appointment from the app | remove erroneous appointments. | +| `* * *` | new user | _see_ the app populated with sample data | see how the app will look when it is in use | +| `* * *` | new user | _access_ a “help” page to view the app’s basic commands | conveniently view basic commands within the app | +| `* * *` | user | _save_ the address book automatically | prevent accidental loss of data | +| `* *` | user | _search_ for specific patients using keywords such as the patient’s name or blood type etc. | find and filter specific patients from a long list of patients | +| `* *` | user | _search_ for a specific medical record of a patient using keywords such as date, condition or medication | find and filter specific medical records from a long list of medical records | +| `* *` | user | _pin_ a specific patient | conveniently view patients details | +| `* *` | user | _unpin_ a specific patient | | +| `* *` | user | _attach_ files such as lab reports and prescription images to a patient's medical records | keep documents in an organised manner | +| `* *` | advanced user | _directly edit_ the MedBook data stored in the JSON file | make specific and controlled changes to the data | +| `* *` | user | _edit_ an existing appointment's details | keep the appointment's details accurate and up-to-date | +| `*` | user | _receive_ regular updates and bug fixes for the app | | +| `*` | user | _receive_ reminders for upcoming patient appointments | be punctual for upcoming appointments | +| `*` | user | _export_ patient data | share or transfer data between different systems | +| `*` | user | _leave_ patient data encrypted | prevent unauthorised access to the data | +| `*` | user | _sort_ existing appointments by date | quickly view the appointments which are soon upcoming | + +--- + +## Use Cases + +### UC01 - Viewing Help + +- **Actor**: User +- **System**: MedBook +- **Main Success Scenario (MSS)**: + 1. User requests for help. + 2. MedBook displays help information.
+ Use case ends. + +--- + +### UC02 - Adding a Patient + +- **Actor**: User +- **System**: MedBook +- **Main Success Scenario (MSS)**: + 1. User requests to add a new patient. + 2. User enters the patient's details. + 3. MedBook adds the patient to the system.
+ Use case ends. +- **Extensions**: + - 2a. MedBook detects an error in the entered patient's details. + - 2a1. MedBook shows an error message. + - 2a2. User enters new patient details. + - Steps 2a1-2a2 are repeated until the patient details entered is correct. + - Use case resumes from step 3. + +--- + +### UC03 - Listing All Patients -*{More to be added}* +- **Actor**: User +- **System**: MedBook +- **Preconditions**: There is at least one patient entry in the system. +- **Main Success Scenario (MSS)**: + 1. User requests to list patients. + 2. MedBook shows a list of patients.
+ Use case ends. -### Use cases +--- -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) +### UC04 - Editing a Patient's Details + +- **Actor**: User +- **System**: MedBook +- **Preconditions**: There is at least one patient entry in the system. +- **Main Success Scenario (MSS)**: + 1. User lists all patients (UC03). + 2. User requests to edit a patient's details. + 3. User enters the new patient details. + 4. MedBook updates the patient details.
+ Use case ends. +- **Extensions**: + - 3a. MedBook detects an error in the entered patient's details. + - 3a1. MedBook shows an error message. + - 3a2. User enters new patient details. + - Steps 3a1-3a2 are repeated until the patient details entered is correct. + - Use case resumes from step 4. -**Use case: Delete a person** +--- -**MSS** +### UC05 - Searching for a Specific Patient + +- **Actor**: User +- **System**: MedBook +- **Preconditions**: There is at least one patient entry in the system. +- **Main Success Scenario (MSS)**: + 1. User lists all patients (UC03). + 2. User requests to search for a specific patient. + 3. User enters search criteria. + 4. MedBook performs a search and displays matching patients.
+ Use case ends. +- **Extensions**: + - 4a. No matches found. + - 4a1. MedBook informs the user that there were no matches. + - Use case ends. -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +--- - Use case ends. +### UC06 - Delete a Patient + +- **Actor**: User +- **System**: MedBook +- **Preconditions**: There is at least one patient entry in the system. +- **Main Success Scenario (MSS)**: + 1. User lists all patients (UC03). + 2. User requests to delete a specific patient. + 3. User enters patient ID. + 4. MedBook deletes the patient.
+ Use case ends. +- **Extensions**: + - 3a. MedBook detects an error in the entered patient's ID. + - 3a1. MedBook shows an error message. + - 3a2. User enters new patient ID. + - Steps 3a1-3a2 are repeated until the patient ID entered is correct. + - Use case resumes from step 4. -**Extensions** +--- -* 2a. The list is empty. +### UC07 - Pin a Patient + +- **Actor**: User +- **System**: MedBook +- **Preconditions**: There is at least one patient entry in the system. +- **Main Success Scenario (MSS)**: + 1. User lists all patients (UC03). + 2. User requests to pin a specific patient. + 3. User enters patient ID. + 4. MedBook pins the patient.
+ Use case ends. +- **Extensions**: + - 3a. MedBook detects an error in the entered patient's ID. + - 3a1. MedBook shows an error message. + - 3a2. User enters new patient's ID. + - Steps 3a1-3a2 are repeated until the patient ID entered is correct. + - Use case resumes from step 4. - Use case ends. +--- -* 3a. The given index is invalid. +### UC08 - Unpin a Patient + +- **Actor**: User +- **System**: MedBook +- **Preconditions**: There is at least one pinned patient. +- **Main Success Scenario (MSS)**: + 1. User requests to unpin a specific patient. + 2. User enters the patient's PINNEDID. + 3. MedBook unpins the patient.
+ Use case ends. +- **Extensions**: + - 2a. MedBook detects an error in the entered PINNEDID. + - 2a1. MedBook shows an error message. + - 2a2. User enters new PINNEDID. + - Steps 2a1-2a2 are repeated until the PINNEDID entered is correct. + - Use case resumes from step 3. - * 3a1. AddressBook shows an error message. +--- - Use case resumes at step 2. +### UC09 - Adding a Record to a Patient + +- **Actor**: User +- **System**: MedBook +- **Preconditions**: There is at least one patient entry in the system. +- **Main Success Scenario (MSS)**: + 1. User lists all patients (UC03). + 2. User requests to add a new record to a patient. + 3. User enters the record's details. + 4. MedBook adds the record to the patient.
+ Use case ends. +- **Extensions**: + - 3a. MedBook detects an error in the entered record's details. + - 3a1. MedBook shows an error message. + - 3a2. User enters new record details. + - Steps 3a1-3a2 are repeated until the record details entered is correct. + - Use case resumes from step 4. -*{More to be added}* +--- + +### UC10 - View Patient's Medical Records + +- **Actor**: User +- **System**: MedBook +- **Main Success Scenario (MSS)**: + 1. User lists all patients (UC03). + 2. User requests to view a specific patient's records. + 3. User enters the patient ID. + 4. MedBook displays the records of the patient.
+ Use case ends. +- **Extensions**: + - 3a. MedBook detects an error in the entered patient's ID. + - 3a1. MedBook shows an error message. + - 3a2. User enters new patient ID. + - Steps 3a1-3a2 are repeated until the patient ID entered is correct. + - Use case resumes from step 4. + +--- + +### UC11 - Editing a Record + +- **Actor**: User +- **System**: MedBook +- **Preconditions**: There is at least one record entry in the patient. +- **Main Success Scenario (MSS)**: + 1. User views a patient’s medical records (UC10). + 2. User requests to edit a patient’s record’s details. + 3. User enters new record details. + 4. MedBook updates the record’s details.
+ Use case ends. +- **Extensions**: + - 3a. MedBook detects an error in the entered record’s details. + - 3a1. MedBook shows an error message. + - 3a2. User enters new record details. + - Steps 3a1-3a2 are repeated until the record details entered is correct. + - Use case resumes from step 4. + +--- + +### UC12 - Deleting a Record under a Patient + +- **Actor**: User +- **System**: MedBook +- **Preconditions**: There is at least one record entry in the patient. +- **Main Success Scenario (MSS)**: + 1. User views a patient’s medical records (UC10). + 2. User requests to delete a patient’s record. + 3. User enters the patient ID and record ID. + 4. MedBook deletes the record.
+ Use case ends. +- **Extension**: + - 3a. MedBook detects an error in the entered patient ID and/or record ID. + - 3a1. MedBook shows an error message. + - 3a2. User enters new patient ID and record ID. + - Steps 3a1-3a2 are repeated until the patient ID and record ID entered is correct. + - Use case resumes from step 4. + +--- + +### UC13 - Searching for Records + +- **Actor**: User +- **System**: MedBook +- **Preconditions**: There is at least one record entry in the patient. +- **Main Success Scenario (MSS)**: + 1. User views a patient (UC10). + 2. User requests to search for a specific records. + 3. User enters search keywords. + 4. MedBook performs a search and displays matching record.
+ Use case ends. +- **Extension**: + - 4a. No matches found. + - 4a1. MedBook informs the user that there were no matches. + - Use case ends. + +--- + +### UC14 - Adding an Appointment to a Patient + +- **Actor**: User +- **System**: MedBook +- **Preconditions**: There is at least one patient entry in the system. +- **Main Success Scenario (MSS)**: + 1. User requests to add a new appointment to a patient. + 2. User enters the appointment's details. + 3. MedBook adds the appointment to the patient.
+ Use case ends. +- **Extensions**: + - 2a. MedBook detects an error in the entered appointment's details. + - 2a1. MedBook shows an error message. + - 2a2. User enters new appointment details. + - Steps 2a1-2a2 are repeated until the appointment details entered is correct. + - Use case resumes from step 3. + +--- + +### UC15 - Viewing Appointments + +- **Actor**: User +- **System**: MedBook +- **Main Success Scenario (MSS)**: + 1. User requests to view appointments. + 2. MedBook shows the user all appointments.
+ Use case ends. + +--- + +### UC16 - Deleting an Appointment + +- **Actor**: User +- **System**: MedBook +- **Preconditions**: There is at least one appointment entry in the system. +- **Main Success Scenario (MSS)**: + 1. User views all appointments (UC15). + 2. User requests to delete a new appointment. + 3. User enters the appointment's ID. + 4. MedBook deletes the appointment.
+ Use case ends. +- **Extensions**: + - 3a. MedBook detects an error in the entered appointment ID. + - 3a1. MedBook shows an error message. + - 3a2. User enters new appointment ID. + - Steps 3a1-3a2 are repeated until the appointment ID entered is correct. + - Use case resumes from step 4. + +--- + +### UC17 - Attaching Files + +- **Actor**: User +- **System**: MedBook +- **Preconditions**: There is at least one record entry in the patient +- **Main Success Scenario (MSS)**: + + 1. User views a patient’s medical records (UC10). + 2. User requests to attach a file to a record of a patient. + 3. User selects a file. + 4. MedBook saves the file to the medical record.
+ Use case ends. + +- **Extension**: + - 3a. User does not choose a file. + - 3a1. MedBook displays an error message. + - Use case ends. + +--- + +### UC18 - Opening Files + +- **Actor**: User +- **System**: MedBook +- **Preconditions**: There is at least one record entry with a file attached in the patient +- **Main Success Scenario (MSS)**: + + 1. User requests to view a file attached to a record. + 2. MedBook opens the file on the user’s default launcher.
+ Use case ends. + +- **Extension**: + - 1a. File does not exist in user’s local storage. + - 1a1. MedBook displays an error message. + - Use case ends. + +--- + +## Glossary + +- **Mainstream OS**: Popular operating systems such as Windows, Linux, Unix, and OS-X. + +- **CLI (Command Line Interface)**: A user interface that allows users to interact with the software using text commands via a console or terminal. + +- **GUI (Graphical User Interface)**: A user interface that allows users to interact with the software through graphical icons and visual indicators, as opposed to text-based interfaces. + +- **Backward Compatibility**: The ability of the system to work with data and interfaces from earlier versions of the software. + +- **Healthcare Professional**: An individual who provides healthcare services, such as doctors, nurses, and medical staff. + +- **Performance Issues**: Any lag, delay, or inefficiency in the software’s response or processing time, especially noticeable when handling a large amount of data. + +- **Patient Information**: Data related to a patient, including but not limited to their personal details, medical history, contact information, and any other relevant information. + +--- + +## Documentation, Logging, Testing, Configuration, Dev-Ops + +- [Documentation guide](Documentation.md) +- [Testing guide](Testing.md) +- [Logging guide](Logging.md) +- [Configuration guide](Configuration.md) +- [DevOps guide](DevOps.md) + +--- + +## Appendix: Requirements + +### Product Scope + +**Target User Profile**: + +- **Primary Users**: Healthcare professionals in private clinics who handle patient information on a regular basis. +- **Platform Preference**: Has a strong inclination towards using desktop applications, particularly those that support Command Line Interfaces. +- **Technical Proficiency**: Is adept at typing. + +**Value Proposition**: + +- **Efficient Patient Management**: Provides a streamlined and efficient solution for managing patient information, ensuring that healthcare professionals can access and modify patient data quickly. +- **Speed and Accessibility**: Designed to be significantly faster than conventional GUI applications, allowing users to execute commands and retrieve patient information in a matter of seconds. +- **Command Line Efficiency**: Leverages the power of CLI to offer advanced users the ability to perform tasks in a more direct and efficient manner, while still maintaining accessibility for those who prefer graphical interfaces. ### Non-Functional Requirements -1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. -2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. -3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. +1. Should work on any mainstream OS with Java 11 or above. + - Rationale: Ensures accessibility for users on different platforms. + - Metric: Application functions correctly on Windows, macOS, and Linux. +2. Supports up to 1000 patients without performance issues. + - Rationale: Ensures scalability for larger clinics. + - Metric: Application performs smoothly with a database of 1000 patients. +3. Faster operation with commands than mouse for proficient typists. + - Rationale: Enhances productivity for users familiar with the command line. + - Metric: Common tasks are completed faster using keyboard shortcuts than GUI. +4. Backward compatible with data from previous versions. + - Rationale: Ensures smooth transition for existing users upgrading to a new version. + - Metric: Users can open and interact with data files from previous versions without issues. +5. Usable by novices. + - Rationale: Ensures the application is accessible to new users. + - Metric: New users can perform basic tasks without having to keep referring to the user manual. +6. Provides comprehensive error messages and guidance for recovery. + - Rationale: Helps users understand what went wrong and how to fix it. + - Metric: Error messages include a description of the issue and steps for resolution. +7. Comprehensive documentation and user guides available. + - Rationale: Provides users with resources to understand and use the application effectively. + - Metric: Documentation covers all features, includes screenshots, and is easy to navigate. + +--- + +## Appendix: Instructions for Manual Testing + + + +**Note**: These instructions only provide a starting point for testers to work on; testers are expected to do more *exploratory* testing. + + +### Launch and Shutdown + +#### Initial Launch + +1. Download and place the jar file in an empty folder. +2. Launch the jar application via the terminal. + - Expected: GUI opens with sample contacts. + +--- + +#### Saving Window Preferences + +1. Resize and reposition the window, then close it. +2. Reopen the application. + - Expected: Window retains its size and position. + +--- + +### Adding a New Patient + +1. Test Case: `addpatient n/John Doe i/T0000000Z e/johndoe@gmail.com p/98765432 g/M a/30 bt/AB+ al/Dust` + + - Expected: New patient "John Doe" is added to the list, details are shown in the status message. + +2. Test Case: `addpatient n/John Doe i/0000000 e/johndoe@gmail.com p/98765432 g/M a/30 bt/AB+ al/Dust` + - Expected: Error message displayed, patient not added due to incorrect format of NRIC +3. Test Case: `addpatient n/John Doe i/T0000000Z e/johndoegmail.com p/98765432 g/M a/30 bt/AB+ al/Dust` + - Expected: Error message displayed, patient not added due to incorrect format of email. +4. Test Case: `addpatient n/John Doe i/T0000000Z e/johndoe@gmail.com p/12 g/M a/30 bt/AB+ al/Dust` + - Expected: Error message displayed, patient not added due to incorrect format of phone number. +5. Test Case: `addpatient n/John Doe i/T0000000Z e/johndoe@gmail.com p/98765432 g/T a/30 bt/AB+ al/Dust` + - Expected: Error message displayed, patient not added because gender can only be `M` or `F`. +6. Test Case: `addpatient n/John Doe i/T0000000Z e/johndoe@gmail.com p/98765432 g/M a/-1 bt/AB+ al/Dust` + - Expected: Error message displayed, patient not added because age can only be a non-negative integer. +7. Test Case: `addpatient n/John Doe i/T0000000Z e/johndoe@gmail.com p/98765432 g/M a/30 bt/AP al/Dust` + - Expected: Error message displayed, patient not added due to incorrect blood type. + +--- + +### Editing a Patient's Details + +1. Prerequisites: Ensure the patient list is displayed and contains the entry you wish to edit. +2. Test Case: `editpatient 1 a/35` + + - Expected: Patient at index 1 has their age updated to 35. Details shown in the status message. + +3. Test Case: `editpatient x a/35` (where x > list size) + - Expected: Error message displayed, patient's details unchanged. + +--- + +### Deleting a Patient + +1. Prerequisites: Ensure the patient list is displayed and contains the entry you wish to delete. +2. Test Case: `delete 1` + + - Expected: Patient at index 1 is deleted. Details shown in status message. + +3. Test Case: `delete 0` + - Expected: Error message displayed, **Patient List** unchanged. +4. Other Test Cases: `delete`, `delete x` (where x > list size) + - Expected: Error message displayed, **Patient List** unchanged. + +--- + +### Pinning a Patient + +1. Prerequisites: Ensure the patient list is displayed and contains the entry you wish to pin. +2. Test Case: `pin 1` + + - Expected: Patient at index 1 is pinned to the **Pinned Patient List**. Details shown in the status message. + +3. Test Case: `pin x` (where x > list size) + - Expected: Error message displayed, **Pinned Patient List** unchanged. + +--- + +### Unpinning a Patient + +1. Prerequisites: Ensure the pinned patient list contains the entry you wish to unpin. +2. Test Case: `unpin 1` + + - Expected: Patient at index 1 of the **Pinned Patient List** is unpinned and no longer displayed in the **Pinned Patient List**. Details shown in the status message. + +3. Test Case: `unpin x` (where x > list size) + - Expected: Error message displayed, **Pinned Patient List** unchanged. + +--- + +### Searching for Patients + +1. Prerequisites: Ensure the patient list contains entries that will match your search term. +2. Test Case: `search John` + + - Expected: List of patients with details matching exactly "John" is displayed. + +--- + +### Adding a Record under Patient + +1. Prerequisites: Ensure the patient list is displayed +2. Test Case: `addrecord 1 d/12-11-2023 2200 c/Fever m/Ibuprofen` + + - Expected: Adds the specified record to the first patient. + +3. Test Case: `addrecord x d/12-11-2023 2200 c/Fever m/Ibuprofen` (where x > size of patient list) + - Expected: Error message displayed, record not added. +4. Test Case: `addrecord 1 d/12112023 c/Fever m/Ibuprofen` + - Expected: Error message displayed suggesting date and time should in the form of dd-mm-yyyy hhmm + +--- + +### View Patient's Medical Records + +1. Prerequisites: Ensure the patient list is displayed and contains the entry you wish to view. +2. Test Case: `view 1` + + - Expected: Medical records of the first patient are displayed, details shown in the status message. + +3. Test Case: `view 0` + + - Expected: Error message displayed, **Medical Record List** unchanged. + +4. Other Test Cases: `view`, `view x` (where x > list size) + + - Expected: Error message displayed, **Medical Record List** unchanged. + +--- + +### Editing a Record's Details + +1. Prerequisites: Ensure the record list of the patient is displayed and contains the entry you wish to edit. +2. Test Case: `editrecord 1/1 c/Fever` -*{More to be added}* + - Expected: The record at index 1 of the Patient at index 1 has its conditions updated to only "Fever". Details shown in the status message. -### Glossary +3. Test Case: `editrecord 1/1 c/Fever m/Paracetamol` -* **Mainstream OS**: Windows, Linux, Unix, OS-X -* **Private contact detail**: A contact detail that is not meant to be shared with others + - Expected: The record at index 1 of the Patient at index 1 has its conditions updated to only "Fever" and medications to only "Paracetamol". Details shown in the status message. --------------------------------------------------------------------------------------------------------------------- +4. Test Case: `editrecord x/1 c/Fever` (where x > patient list size) -## **Appendix: Instructions for manual testing** + - Expected: Error message displayed, record's details unchanged. -Given below are instructions to test the app manually. +5. Test Case: `editrecord 1/x c/Fever` (where x > record list size) -
:information_source: **Note:** These instructions only provide a starting point for testers to work on; -testers are expected to do more *exploratory* testing. + - Expected: Error message displayed, record's details unchanged. -
+6. Test Case: `editrecord 1/1 d/12112023` -### Launch and shutdown + - Expected: Error message displayed suggesting date and time should in the form of "dd-mm-yyyy hhmm". + +7. Test Case: `editrecord 1/1 d/12-11-2023 2200 d/13-11-2023 2200` + - Expected: Error message displayed suggesting multiple inputs of date are not allowed. + +--- + +### Deleting a Record + +1. Prerequisites: Ensure the record list of the patient is displayed and contains the entry you wish to edit. +2. Test Case: `deleterecord 1/1` + + - Expected: Deletes the first record of the first patient. + +3. Test Case: `deleterecord x/1` (where x > size of patient list) + + - Expected: Error message displayed, **Medical Record List** unchanged. + +4. Test Case: `deleterecord 1/y` (where y > size of record list of the first patient) + - Expected: Error message displayed, **Medical Record List** unchanged. + +--- + +### Searching Records of the Patient Being Viewed + +1. Prerequisites: Ensure the record list of the patient is displayed. +2. Test Case: `searchrecord Ibuprofen` + + - Expected: List of records with "Ibuprofen" in the details is displayed. + +--- + +### Attaching File to Record + +1. Prerequisites: Ensure the record list of the patient is displayed and it is not empty. +2. Test Case: Click on “Attach Files” and select file from local storage. + + - Expected: “File successfully attached” is displayed and file link is added to record. + +3. Test Case: Click on “Attach Files” and cancel the file explorer without selecting a file + + - Expected: Error message displayed as no file was selected. + +--- + +### Opening File in Record + +1. Prerequisites: Ensure the record list of the patient is displayed and it is not empty. Ensure the record has a file attached. +2. Test Case: Click on Filepath link. + + - Expected: File is opened using the user’s default launcher. + +3. Test Case: Delete or relocate file in local storage. Click on Filepath link. + + - Expected: Error message displayed as file path no longer exists + +--- + +### Adding an Appointment + +1. Prerequisites: Ensure the patient list is displayed and contains the entry you wish to add an appointment to. +2. Test Case: `addappointment 1 n/Eye Exam d/18-10-2023 1900` + + - Expected: New appointment, "Eye Exam" is added to the patient with index 1, details are shown in the status message. + +3. Test Case: `addappointment x n/Eye Exam d/18-10-2023 1900` (where x > list size) + - Expected: Error message displayed, appointment not added. +4. Test Case: `addappointment 1 d/18-10-2023 1900` + - Expected: Error message displayed, appointment not added. +5. Test Case: `addappointment 1 n/Eye Exam` + - Expected: Error message displayed, appointment not added. +6. Test Case: `addappointment 1 n/Eye Exam d/18/10/2023 1900` + - Expected: Error message displayed, appointment not added. + +--- + +### Viewing Appointments + +#### Appointment Window is closed + +1. Test Case: `viewappointment` + - Expected: Appointment Window opens. + +#### Appointment Window is open but not in focus + +1. Test Case: `viewappointment` + - Expected: Appointment Window focuses. + +--- + +### Deleting an Appointment + +1. Prerequisites: Ensure the appointment list is displayed and contains the entry you wish to delete. +2. Test Case: `deleteappointment 1` + - Expected: Appointment with index 1 is deleted, details are shown in status message. +3. Test Case: `deleteappointment x` (where x > list size) + - Expected: Error message displayed, **Appointment List** unchanged. + +--- + +### Verifying Patient Data Integrity + +#### After Operations + +1. Prerequisites: Perform operations like adding, deleting, and editing patients. +2. Navigate to the folder where data is stored and open the data file. + - Expected: All changes should be accurately reflected in the data file. + +--- + +### Handling Invalid Commands + +#### Input Mistakes + +1. Test Case: `addreocrd n/John Doe i/A0000000A a/30 g/M e/jd@example.com p/98776543 bt/AB+ al/Dust` + + - Expected: Error message displayed stating unknown command. + +2. Test Case: `delet 1` + - Expected: Error message displayed stating unknown command. + +## Appendix: Planned Enhancements + +### Additional Navigation in Appointment Calender + +#### Current Implementation + +The current version of the Appointment Calendar allows users to view appointments in a calendar format. Navigation through the calendar is limited to sequential month traversal using 'Next' and 'Previous' buttons. This implementation, while functional, can be cumbersome for users who wish to view appointments that are several months away from the current date. + +#### Proposed Enhancement + +We plan to enhance the calendar navigation by introducing a more efficient way for users to view appointments. The enhancement will allow users to: + +**Manual Selection of Month and Year**: A dropdown menu or a picker control will be integrated into the UI, enabling users to quickly jump to a specific month and year without sequentially navigating through each month. + +**`viewcalender MM YYYY` command**: For users who prefer typing through the CLI, we will implement a command that allows them to view the calendar for a specific month and year. Users will be able to enter a command in the format viewcalendar MM YYYY (e.g., viewcalendar 12 2023 to view December 2023), and the calendar will update to display the selected month and year. + +--- + +### Enhanced Appointment Calender UI + +#### Current Implementation + +The existing UI of the Appointment Calendar displays a truncated version of the appointment name and limits the visible appointments to only two per day. This restriction can lead to incomplete information visibility which is not optimal for efficiency and user experience. + +#### Proposed Enhancement + +To improve the user interface and overall user experience of the Appointment Calendar, the following enhancements are proposed: + +**Expanded Appointment Visibility**: Modify the calendar layout to allow for the display of more than two appointments per day. This could involve redesigning the day cells to accommodate more entries or implementing a scrolling mechanism within each day cell for days with numerous appointments. +Ensure that the UI remains uncluttered and user-friendly, even with the inclusion of more appointment entries per day. + +**Full Appointment Name Display**: Adjust the UI to display the full name of each appointment, rather than a truncated version. This will enable users to quickly identify appointments at a glance without needing to hover over or click into the appointment for full details. +Implement a dynamic text resizing or wrapping feature within each calendar entry to ensure that longer appointment names fit within the allocated space without compromising readability. + +**Responsive and Adaptive Design**: Enhance the calendar’s responsive design so it adapts effectively to different screen sizes and resolutions. This ensures that the increased information density does not negatively impact users on smaller screens or mobile devices. + +--- + +### Enhanced NRIC Parameter for Patients + +#### Current Implementation + +In the current implementation the NRIC parameter for patient identification is limited in its format. It accepts an entry consisting of an alphabet, followed by seven digits, and then another alphabet. This format, while broadly useful, does not align completely with real-world scenarios, particularly in Singapore, and lacks the flexibility required for foreign patients. + +#### Proposed Enhancement + +To make the NRIC parameter more inclusive and reflective of real-world use cases, especially in Singapore, we propose enhancing the NRIC parameter with the following features: + +**Restricted First Alphabet**: The first alphabet in the NRIC will now be restricted to specific letters, such as 'S' and 'T', which are currently used in Singapore. This change aligns the system more closely with the actual format of NRICs in Singapore. + +**Optional Passport Number Support**: To accommodate foreign patients who do not have an NRIC, the system will be enhanced to accept passport numbers as an alternative identifier. This feature is particularly important for private clinics that cater to a diverse patient base, including non-residents and tourists. + +--- + +### Accepting `/` in Name parameter + +#### Current Implementation + +Due to current constraints in the Parser which causes / to be parsed as tags, the "/" character cannot be entered into the name parameter. As such, users would currently not be able to enter "Muhammed Ali s/o Muhammed Ali". + +#### Proposed Enhancement + +Modify the parsing logic to differentiate between command tags and legitimate use of special characters in names. This could involve implementing an escape character mechanism or a more advanced parsing algorithm that can contextually understand the use of "/" in different scenarios. + +--- + +### Patient Index Alignment + +#### Current Implementation + +The Patient ID in the patients in the **Pinned Patient List** and **Patient Being Viewed** is unaligned with the **Patient List** which might lead to confusion in users for which Patient ID to follow. + +#### Proposed Enhancement + +Modify the way panels create new person card entries. This could involve creating new lists to keep track of the patient index and update commands associated with the Pinned Patients and Patient Being Viewed. + +--- -1. Initial launch +## Appendix: Effort - 1. Download the jar file and copy into an empty folder +This appendix aims to an insight into the total effort that went into the development of Medbook. - 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. +### Effort Overview -1. Saving window preferences +**Duration:** The project spanned approximately 2 months from initial conception to final release. +**Team:** Consisted of 5 members. - 1. Resize the window to an optimum size. Move the window to a different location. Close the window. +#### Technical Complexity and Challenges - 1. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained. +- **Data Security:** Implementing robust security measures such as data encryption to protect sensitive patient information was paramount and required extensive research and testing. (It was omitted due to constraints related to the Project) +- **Defensive Coding:** We enforced immutability of `Person`, `Record`, and `Appointment` to maintain data integrity. +- **User Interface:** Crafting an intuitive GUI that also supported a CLI for efficiency demanded iterative design and usability testing. -1. _{ more test cases …​ }_ +#### Effort Quantification -### Deleting a person +- **Development:** The team invested over 300 hours in coding, with a focused effort on crafting intuitive and responsive GUI and CLI interfaces. This investment reflects our commitment to usability and accessibility, ensuring that both novice and experienced users can navigate the application with ease. -1. Deleting a person while all persons are being shown +- **Testing:** More than 100 hours were dedicated to a combination of manual and automated testing. This rigorous testing protocol was critical in validating the application's reliability and optimizing its performance across various user scenarios and system environments. - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. +- **Documentation:** We allocated 50 hours to developing thorough user documentation. This comprehensive guide is pivotal for facilitating a quick adoption of the application by new users and serves as a reliable reference for existing users to leverage the application's full potential. - 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. +#### Reuse of Existing Solutions - 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. +- **Library Reuse:** Utilized the JavaFX library for the GUI, which saved much effort for front-end development. - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous. +#### Effort Comparison with Reference Projects: -1. _{ more test cases …​ }_ +- **Compared to AB3:** the development of Medbook demanded considerably more effort, primarily due to its capability to manage multiple entity types such as patients, records, and appointments. This multifaceted approach contrasts with AB3's design, which is centered around handling a single entity type. + Moreover, Medbook was built upon the AB3 architecture as a foundational base, necessitating a deep understanding of the existing complex framework. Even though certain features in Medbook were adapted from AB3 to suit our specific needs, significant effort was required to modify and extend these features. Tailoring pre-existing functionalities to fit into our more comprehensive application model involved intricate work, ensuring seamless integration and functionality within Medbook's broader scope. -### Saving data +#### Achievements -1. Dealing with missing/corrupted data files +- Despite the high complexity, the team managed to deliver Medbook on schedule. +- User feedback has been overwhelmingly positive, especially regarding the ease of use and performance of the application. - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ +#### Conclusion -1. _{ more test cases …​ }_ +The successful development of Medbook is a testament to the well-coordinated effort, rigorous testing, and effective project management that adapted to challenges and complexity with innovative solutions and strategic planning. diff --git a/docs/Documentation.md b/docs/Documentation.md index 3e68ea364e7..082e652d947 100644 --- a/docs/Documentation.md +++ b/docs/Documentation.md @@ -1,29 +1,21 @@ --- -layout: page -title: Documentation guide + layout: default.md + title: "Documentation guide" + pageNav: 3 --- -**Setting up and maintaining the project website:** - -* We use [**Jekyll**](https://jekyllrb.com/) to manage documentation. -* The `docs/` folder is used for documentation. -* To learn how set it up and maintain the project website, follow the guide [_[se-edu/guides] **Using Jekyll for project documentation**_](https://se-education.org/guides/tutorials/jekyll.html). -* Note these points when adapting the documentation to a different project/product: - * The 'Site-wide settings' section of the page linked above has information on how to update site-wide elements such as the top navigation bar. - * :bulb: In addition to updating content files, you might have to update the config files `docs\_config.yml` and `docs\_sass\minima\_base.scss` (which contains a reference to `AB-3` that comes into play when converting documentation pages to PDF format). -* If you are using Intellij for editing documentation files, you can consider enabling 'soft wrapping' for `*.md` files, as explained in [_[se-edu/guides] **Intellij IDEA: Useful settings**_](https://se-education.org/guides/tutorials/intellijUsefulSettings.html#enabling-soft-wrapping) +# Documentation Guide +* We use [**MarkBind**](https://markbind.org/) to manage documentation. +* The `docs/` folder contains the source files for the documentation website. +* To learn how set it up and maintain the project website, follow the guide [[se-edu/guides] Working with Forked MarkBind sites](https://se-education.org/guides/tutorials/markbind-forked-sites.html) for project documentation. **Style guidance:** * Follow the [**_Google developer documentation style guide_**](https://developers.google.com/style). +* Also relevant is the [_se-edu/guides **Markdown coding standard**_](https://se-education.org/guides/conventions/markdown.html). -* Also relevant is the [_[se-edu/guides] **Markdown coding standard**_](https://se-education.org/guides/conventions/markdown.html) - -**Diagrams:** - -* See the [_[se-edu/guides] **Using PlantUML**_](https://se-education.org/guides/tutorials/plantUml.html) -**Converting a document to the PDF format:** +**Converting to PDF** -* See the guide [_[se-edu/guides] **Saving web documents as PDF files**_](https://se-education.org/guides/tutorials/savingPdf.html) +* See the guide [_se-edu/guides **Saving web documents as PDF files**_](https://se-education.org/guides/tutorials/savingPdf.html). diff --git a/docs/Gemfile b/docs/Gemfile deleted file mode 100644 index c8385d85874..00000000000 --- a/docs/Gemfile +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -source "https://rubygems.org" - -git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } - -gem 'jekyll' -gem 'github-pages', group: :jekyll_plugins -gem 'wdm', '~> 0.1.0' if Gem.win_platform? -gem 'webrick' diff --git a/docs/Logging.md b/docs/Logging.md index 5e4fb9bc217..589644ad5c6 100644 --- a/docs/Logging.md +++ b/docs/Logging.md @@ -1,8 +1,10 @@ --- -layout: page -title: Logging guide + layout: default.md + title: "Logging guide" --- +# Logging guide + * We are using `java.util.logging` package for logging. * The `LogsCenter` class is used to manage the logging levels and logging destinations. * The `Logger` for a class can be obtained using `LogsCenter.getLogger(Class)` which will log messages according to the specified logging level. diff --git a/docs/SettingUp.md b/docs/SettingUp.md index 275445bd551..03df0295bd2 100644 --- a/docs/SettingUp.md +++ b/docs/SettingUp.md @@ -1,27 +1,32 @@ --- -layout: page -title: Setting up and getting started + layout: default.md + title: "Setting up and getting started" + pageNav: 3 --- -* Table of Contents -{:toc} +# Setting up and getting started + + -------------------------------------------------------------------------------------------------------------------- ## Setting up the project in your computer -
:exclamation: **Caution:** + +**Caution:** Follow the steps in the following guide precisely. Things will not work out if you deviate in some steps. -
+ First, **fork** this repo, and **clone** the fork into your computer. If you plan to use Intellij IDEA (highly recommended): 1. **Configure the JDK**: Follow the guide [_[se-edu/guides] IDEA: Configuring the JDK_](https://se-education.org/guides/tutorials/intellijJdk.html) to to ensure Intellij is configured to use **JDK 11**. -1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA.
- :exclamation: Note: Importing a Gradle project is slightly different from importing a normal Java project. +1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA. + + Note: Importing a Gradle project is slightly different from importing a normal Java project. + 1. **Verify the setup**: 1. Run the `seedu.address.Main` and try a few commands. 1. [Run the tests](Testing.md) to ensure they all pass. @@ -34,10 +39,11 @@ If you plan to use Intellij IDEA (highly recommended): If using IDEA, follow the guide [_[se-edu/guides] IDEA: Configuring the code style_](https://se-education.org/guides/tutorials/intellijCodeStyle.html) to set up IDEA's coding style to match ours. -
:bulb: **Tip:** + + **Tip:** Optionally, you can follow the guide [_[se-edu/guides] Using Checkstyle_](https://se-education.org/guides/tutorials/checkstyle.html) to find how to use the CheckStyle within IDEA e.g., to report problems _as_ you write code. -
+ 1. **Set up CI** diff --git a/docs/Testing.md b/docs/Testing.md index 8a99e82438a..78ddc57e670 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -1,12 +1,15 @@ --- -layout: page -title: Testing guide + layout: default.md + title: "Testing guide" + pageNav: 3 --- -* Table of Contents -{:toc} +# Testing guide --------------------------------------------------------------------------------------------------------------------- + + + + ## Running tests @@ -19,8 +22,10 @@ There are two ways to run tests. * **Method 2: Using Gradle** * Open a console and run the command `gradlew clean test` (Mac/Linux: `./gradlew clean test`) -
:link: **Link**: Read [this Gradle Tutorial from the se-edu/guides](https://se-education.org/guides/tutorials/gradle.html) to learn more about using Gradle. -
+ + +**Link**: Read [this Gradle Tutorial from the se-edu/guides](https://se-education.org/guides/tutorials/gradle.html) to learn more about using Gradle. + -------------------------------------------------------------------------------------------------------------------- diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 57437026c7b..8656f868fbd 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,197 +1,929 @@ --- -layout: page -title: User Guide +layout: default.md +title: "User Guide" +pageNav: 4 --- -AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps. +# User Guide -* Table of Contents -{:toc} +## Welcome to MedBook! --------------------------------------------------------------------------------------------------------------------- +**_Elevate Your Clinic with Advanced Patient Management Solutions_**
+ +Welcome to MedBook, the revolutionary **desktop application** tailor-made for **doctors** and **medical administrative assistants** in **private clinics**. MedBook is your partner in digitizing patient management and medical records, ensuring efficiency, accessibility, and security. + +#### Why Choose MedBook? + +With MedBook, experience a new level of convenience and control: + +- **Streamlined Patient Management**: With intuitive tools, adding, editing, removing, and saving patient details becomes a task of mere seconds. +- **Integrated Data Handling**: Effortlessly manage patient appointments and medical records from a single, unified platform. +- **Instant Information Access**: Retrieve any required information with speed and precision. +- **Comprehensive Patient Overview**: View patient details, medical history, and upcoming appointments in one comprehensive and easily navigable interface. + +#### Unlock the Potential of Digital Healthcare with MedBook + +- **Go Digital with Ease**: Transition your clinic to a digital platform effortlessly, backed by features tailored for the healthcare sector's unique needs. +- **Optimized for Speed**: Catering to all skill levels, MedBook's **Command Line Interface (CLI)** and **Graphical User Interface (GUI)** provide options for rapid data entry and user-friendly navigation. +- **Begin Your MedBook Journey**: New to MedBook? Click [here](#quick-start) for a step-by-step guide to revolutionizing your clinic's management system. + +--- + +
+ +## Table of Contents + +- [Welcome to MedBook](#welcome-to-medbook) +- [Table of Contents](#table-of-contents) +- [Navigating the User Guide](#navigating-the-user-guide) +- [Quick Start](#quick-start) +- [Glossary](#glossary) +- [Navigating the GUI](#navigating-the-gui) +- [MedBook Tutorial](#medbook-tutorial) +- [Features](#features) + - [Parameters](#parameters) + - [Patient Features](#patient-features) + - [Adding a patient](#adding-a-patient-addpatient) + - [Editing a patient](#editing-a-patient-editpatient) + - [Deleting a patient](#deleting-a-patient-delete) + - [Searching patients by keywords](#searching-patients-by-keywords-search) + - [Listing all patients](#listing-all-patients-list) + - [Pinning a patient](#pinning-a-patient-pin) + - [Unpinning a patient](#unpinning-a-patient-unpin) + - [Medical Record Features](#medical-record-features) + - [Adding a medical record](#adding-a-medical-record-addrecord) + - [Viewing patient medical records](#viewing-patient-medical-records-view) + - [Editing a medical record](#editing-a-medical-record-editrecord) + - [Deleting a medical record](#deleting-a-medical-record-deleterecord) + - [Searching medical records by keywords](#searching-medical-records-by-keywords-searchrecord) + - [Attaching files to a patient's medical record](#attaching-files-to-a-patient-s-medical-record) + - [Appointment Features](#appointment-features) + - [Adding an appointment](#adding-an-appointment-addappointment) + - [Deleting an appointment](#deleting-an-appointment-deleteappointment) + - [Viewing appointments](#viewing-appointments-viewappointment) + - [General Features](#general-features) + - [Viewing help](#viewing-help-help) + - [Exiting the program](#exiting-the-program-exit) + - [Saving the data](#saving-the-data) + - [Editing the data file](#editing-the-data-file) +- [FAQ](#faq) +- [Known Issues](#known-issues) +- [Command Summary](#command-summary) + +--- + +
+ +## Navigating the User Guide + +Welcome to the MedBook User Guide! Our goal is to empower you with the knowledge and confidence to make the most of MedBook's features. + +- **Effortless Navigation**: Use the table of contents for seamless navigation between sections. +- **Quick Start for New Users**: If you're new to MedBook, start with the [Quick Start](#quick-start) section to get up and running. +- **Explore Features**: Already familiar with the basics? Dive into the [Features](#features) section to discover all that our application has to offer. + +We're here to make your experience as user-friendly as possible. Let's get started! + +[Back to Table Of Contents](#table-of-contents) + +--- ## Quick start -1. Ensure you have Java `11` or above installed in your Computer. +1. **Check Java Installation**: + + - Ensure you have Java `11` or above installed on your computer: + + - [How do I check my version of Java?](#faq) + +2. **Download MedBook**: + + - Grab the latest version of MedBook (`medbook.jar`) from our [Github Release](https://github.com/AY2324S1-CS2103T-T12-4/tp/releases). + +3. **Set Up Your Workspace**: + + - Choose a folder where you'd like to manage your patient data. + + + + **Tip**: Create a new folder named `MedBook` for easy organization. + + + + - Move `medbook.jar` into the folder as shown below. + +4. **Accessing Terminal**: + + - **Windows Users**: + + - Search for "Terminal" in the Windows search bar and launch it. + + - **Mac Users**: + - Find **Terminal** in "Utilities" under "Applications". Or, you can use Spotlight Search (Command + Spacebar) and type "Terminal". + +5. **Launch MedBook**: + + - Navigate to to the folder where you have stored `medbook.jar` using the `cd` command + + + + **Tip**: Right-click the folder and select `New Terminal at Folder` (**Mac**) or `Open in Terminal` (**Windows**) to instantly navigate to the folder in your **Terminal**! + + + + - Type `java -jar medbook.jar` and hit Enter to start MedBook.
+ +Once launched, MedBook will look like this: +![Ui](images/MedBookUi.png) + +- **Exploring Further**: + - Learn more about navigating the GUI [here](#navigating-the-gui). + - For new users, learn how to use MedBook [here](#medbook-tutorial). + - For advanced users, view all the available features [here](#features). -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). +[Back to Table Of Contents](#table-of-contents) -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +--- + +
+ +## Glossary -1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar addressbook.jar` command to run the application.
- A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
- ![Ui](images/Ui.png) +Here are some definitions of the terms used throughout this user guide. -1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- Some example commands you can try: +| **Term** | **Definition** | +| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Graphical User Interface (GUI) | The GUI allows you to interact with the application through things you can click, instead of typing commands. It also provides visual display for information stored in the application. | +| Command Line Interface (CLI) | The CLI is a way of interacting with the application using typed text commands. | +| Command | A command is a textual input that users type into the Command Line Interface to interact with the application. | +| Parameter | A parameter is a specific piece of information or data that you provide to a command in a Command Line Interface to customize the command's action. | + +[Back to Table Of Contents](#table-of-contents) + +--- - * `list` : Lists all contacts. +
- * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book. +## Navigating the GUI - * `delete 3` : Deletes the 3rd contact shown in the current list. +**Main Window**: +![Ui Component](images/UiComponent.png) - * `clear` : Deletes all contacts. +| **Name of Component** | **Description** | +| --------------------- | -------------------------------------------------------------------------------------------------------- | +| Menu Bar | Displays the drop down menus for MedBook, such as **File** and **Help** | +| Message Display Box | Display the message output for every command. It can either can be an error or success message | +| Patient Card | Contains information of the patient such as name, age, and contact information | +| Patient List | Displays a list of Patient Cards | +| Patient Being Viewed | The Patient Card of the patient currently being viewed | +| Record Card | Contains information of the medical record including date and time, condition, and prescribed medication | +| Medical Records List | Displays a list of Record Cards belonging to the Patient Being Viewed | +| Pinned Patient List | Displays a list of Patient Cards of pinned patients | +| Command Input Box | A text box which allows users to input commands | - * `exit` : Exits the app. +
+ +**Appointment Window** +![Appointment Window](images/ViewAppointmentWindow.png) + +| **Name of Component** | **Description** | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Appointment Card | Contains information of the appointment including name, date and time and NRIC | +| Appointment List | Displays a list of Appointment Cards | +| Calendar | Presents appointments on their specific dates. Each date shows the abbreviated `NAME` of up to the **first two** appointments from the **Appointment List**. | +| Previous Button | Shifts the calendar to the previous month | +| Next Button | Shifts the calendar to the next month | + +[Back to Table Of Contents](#table-of-contents) + +--- -1. Refer to the [Features](#features) below for details of each command. +
--------------------------------------------------------------------------------------------------------------------- +## MedBook Tutorial + +1. **Starting MedBook**: + + - Launch the MedBook application. You may refer to [here](#quick-start). + + + +**Note**: Upon starting, MedBook will display some sample data to help you get familiar. + + + +2. **Opening the Help Window**: + - Let us try opening the **Help Window**. Type `help` in the Command Input Box and press Enter. + - A **Help Window**, similar to the image below, will appear, listing all available commands.

+ Please refer to the [Features](#features) section for a more detailed explanation of the commands. +3. **Adding a New Patient**: + - Now, let us try adding a new patient. Type in `addpatient n/John Doe i/A0000000B e/johndoe@gmail.com p/12345678 g/M a/26 bt/AB+ al/Penicillin` and press Enter. + - The Message Display Box will confirm the successful addition.
+4. **Practice Adding Patients**: + - Experiment by adding a few more patients to MedBook on your own! +5. **Adding a Medical Record**: + - Now, let us try adding a medical record to a patient. Type in `addrecord 3 d/18-09-2023 1800 c/Fever m/Paracetamol` and press Enter. + - This will add a new medical record to the third patient shown in the **Patient List**. + - The Message Display Box will confirm the successful creation.
+6. **Add More Medical Records**: + - Try creating additional medical records for other patients in MedBook! +7. **Viewing Patient Records**: + - Let us view the medical records of the first patient. Type in `view 1` and press Enter. + - If no medical records were added for the first patient, you will see an empty **Medical Record List** as shown. +
+ +Congratulations! You are now ready to explore MedBook on your own. Experiment with different features in the following section and discover the potential of MedBook! + +[Back to Table Of Contents](#table-of-contents) + +--- + +
## Features -
+ -**:information_source: Notes about the command format:**
+**Notes about the command format:**
-* Words in `UPPER_CASE` are the parameters to be supplied by the user.
- e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. +- **Upper Case Words**: Words in `UPPER_CASE` indicate parameters to be supplied by the user.
-* Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. + - Example: in `addpatient n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. -* Items with `…`​ after them can be used multiple times including zero times.
- e.g. `[t/TAG]…​` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. +- **Optional Items**: Square brackets `[ ]` denote optional elements.
-* Parameters can be in any order.
- e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. + - Example: `n/NAME [al/ALLERGIES]` can be entered as either `n/John Doe al/Pencillin` or just `n/John Doe`. -* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
- e.g. if the command specifies `help 123`, it will be interpreted as `help`. +- **Repeated Use**: Ellipsis `…` after an item means it can be used multiple times.
-* If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application. -
+ - Example: `[al/ALLERGIES]…​` can be used as `al/Pencillin`, `al/Pollen al/Dust` or not at all. -### Viewing help : `help` +- **Flexible Parameter Order**: The order of parameters in a command can vary.
-Shows a message explaning how to access the help page. + - Example: Both `n/NAME p/PHONE_NUMBER` and `p/PHONE_NUMBER n/NAME` are valid. -![help message](images/helpMessage.png) +- **Extraneous parameters**: Additional parameters for commands that do not require them (such as `help`, `list`, and `exit`) will be ignored.
-Format: `help` + - Example: `help 123` will be processed just as `help`. + +- **Unique Identifiers**: **Must be a positive integer** like 1, 2, 3, …​ + + - `PATIENTID`: The unique identification for a patient shown in the **Patient List**.
+ + - `RECORDID`: The unique identification for a medical record shown in the displayed **Medical Record List** of a specific patient.
+ + - `APPOINTMENTID`: The unique identification for an appointment shown in the displayed **Appointment List**.
+ + - `PINNEDID`: The unique identification for a patient shown in the displayed **Pinned Patient List**.
+ + + +
+ +### Parameters + +| FIELD | PREFIX | CONSTRAINTS | +| ------------ | ------ | ----------------------------------------------------------------------------------------------------- | +| `NAME` | n | Alphanumeric characters, dashes, dots and spaces only | +| `NRIC` | i | Starts with a letter, followed by seven digits, and ends with a letter. Letters are case-insensitive. | +| `EMAIL` | e | Must follow the format **local-part@domain** | +| `GENDER` | g | Either **M** (Male) or **F** (Female) | +| `PHONE` | p | A sequence of at least 3 digits long | +| `AGE` | a | A non-negative integer | +| `BLOODTYPE` | bt | Must be one of the following: **A-**, **A+**, **B-**, **B+**, **AB-**, **AB+**, **O-**, **O+** | +| `ALLERGY` | al | Alphanumeric characters only | +| `DATETIME` | d | Must follow the format **dd-MM-yyyy HHmm** | +| `CONDITION` | c | Alphanumeric characters, dashes and spaces | +| `MEDICATION` | m | Alphanumeric characters, dashes and spaces | + + + +**Notes for `NAME` format:**
+ +- If the patient's name contains the `/` character, use the `-` character instead. For example, use `s-o` instead of `s/o`. +
+ + + +**Notes for `NRIC` format:**
+ +- If the patient does not have an NRIC, eg. foreigners, a placeholder NRIC which is not valid such as `A1234567A` can be used instead. +
+ + + +**Notes for `EMAIL` format:**
+ +1. The **local-part** should only contain alphanumeric characters and these special characters: `+`, `_`, `.`, `-`. The **local-part** may not start or end with any special characters.
+2. This is followed by an `@` and then a **domain** name. The **domain** name is made up of **domain** labels separated by periods.
-### Adding a person: `add` +The **domain** name must:
-Adds a person to the address book. +- end with a **domain** label at least 2 characters long
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +- have each **domain** label start and end with alphanumeric characters
-
:bulb: **Tip:** -A person can have any number of tags (including 0) -
+- have each **domain** label consist of alphanumeric characters, separated only by hyphens, if any. +
+ + + +**Notes for `ALLERGY` format:**
+ +- Allergies that consist of multiple words should be consolidated into a single word with each word's initial letter capitalized. For example, use `RedMeat` instead of `Red Meat`. +
+ +[Back to Table Of Contents](#table-of-contents) + +--- + +
+ +### Patient Features + +#### Adding a patient: `addpatient` + +Adds a new patient to the system. + +Format: `addpatient n/NAME i/NRIC e/EMAIL p/PHONE g/GENDER a/AGE bt/BLOODTYPE [al/ALLERGY]...` + +- Adds a patient with information including: `NAME`, `NRIC`, `EMAIL`, `PHONE`, `GENDER`, `AGE`, `BLOODTYPE`, `ALLERGY`. + + + +**Tip**: Want to add a new patient to MedBook? Try out the `addpatient` command! + + + +Example: + +`addpatient n/John Doe i/T1234567A e/johndoe@gmail.com p/12345678 g/M a/26 bt/AB+ al/Penicillin` +Adds a **Male** patient named **John Doe** whose NRIC is **T1234567A** , **26** years old, has **AB+** Blood Type, and is allergic to **Penicillin**. +His email and phone number is **johndoe@gmail.com** and **12345678** respectively. + +Screenshots: + + +Command + + +Output + + +[Back to Table Of Contents](#table-of-contents) + +--- + +
+ +#### Editing a patient: `editpatient` + +Edits the details of an existing patient. + +Format: `editpatient PATIENTID PREFIX/NEWVALUE...` + +- Edits the details of the patient with the corresponding `PATIENTID`.
+ +- Acceptable fields: `NAME`, `EMAIL`, `PHONE`, `GENDER`, `AGE`, `BLOODTYPE`, `ALLERGY`.
+ + + +**Note**: `NRIC` cannot be edited. + + + +- Existing values in the respective fields will be updated to the `NEWVALUE`.
+ +- Multiple entries of `ALLERGY` are allowed i.e `editpatient 1 al/Seafood al/Dust`.
+ + + +**Note**: When editing `ALLERGY`, the existing allergies of the patient will be removed i.e adding of allergies is not cumulative. + + + +Examples: + +- `editpatient 1 e/johndoe_updated@gmail.com` Edits the `EMAIL` of the patient with the `PATIENTID` of **1** to **johndoe_updated@gmail.com**.
+ +- `editpatient 2 p/92345678` Edits the `PHONE` of the patient with the `PATIENTID` of **2** to **92345678**.
+ +- `editpatient 3 al/aspirin` Edits the `ALLERGY` of the patient with the `PATIENTID` of **3** to **aspirin**.
+ +- `editpatient 2 p/92345678 al/aspirin` Edits the `PHONE` and `ALLERGY` of the patient with the `PATIENTID` of **2** to **92345678** and **aspirin**, respectively.
+ +Screenshots: + + +Command + + +Output + + +[Back to Table Of Contents](#table-of-contents) + +--- + +#### Deleting a patient: `delete` + +Deletes an existing patient from the system. + +Format: `delete PATIENTID` + +- Deletes the patient with the corresponding `PATIENTID`. + +Example: + +- `delete 2` Deletes the patient with the `PATIENTID` of **2**. + +Screenshots: + + +Command + + +Output + + +[Back to Table Of Contents](#table-of-contents) + +--- + +
+ +#### Searching patients by keywords: `search` + +Searches for patients with details containing the corresponding `KEYWORD`. + +Format: `search KEYWORD [MOREKEYWORDS]...` + +- The search is not case-sensitive. e.g **Penicillin** will match **penicillin**.
+ +- The order of the keywords does not matter. e.g. **Hans Bo** will match **Bo Hans**. + +- Patients matching at least one keyword will be returned. e.g. **Hans Bo** will return **Hans Gruber**, **Bo Yang**.
+ +- Patients with detail that are not exactly the same as the `KEYWORD` searched will not appear. e.g. **Han** will not return **Hans**.
+ + + +**Note**: The details of the patient's medical records will not be searched. + + + + + +**Tip**: Utilise the `search` command to quickly locate patients. It is perfect for filtering patient lists - for instance, identifying patients with specific allergies. + + Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` -### Listing all persons : `list` +- `search Alex` Will display patients with **Alex** in their details.
-Shows a list of all persons in the address book. +- `search Alex M` Will display patients with **Alex** or **M** in their details. + +Screenshots: + + +Command + + +Output + + +[Back to Table Of Contents](#table-of-contents) + +--- + +
+ +#### Listing all patients: `list` + +Shows a list of all patients. Format: `list` -### Editing a person : `edit` +- **All** patients will be shown in the **Patient List**. + + + +**Tip**: The `list` command allows you to easily view all the patients in the **Patient List**. For instance, after you've used the `search` command to find specific patients, you can use the `list` command to gather all the patients conveniently! -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +[Back to Table Of Contents](#table-of-contents) -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …​ -* At least one of the optional fields must be provided. -* Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person’s tags by typing `t/` without - specifying any tags after it. +--- + +#### Pinning a patient: `pin` + +Pins an existing patient. + +Format: `pin PATIENTID` + +- Pins the patient with the corresponding `PATIENTID` to the **Pinned Patient List**. + + + +**Tip**: A patient requires follow-up? Use the `pin` command to help you remember to contact them! + + + +Example: + +- `pin 2` Pins the patient with the `PATIENTID` of **2** to the **Pinned Patient List**. + +Screenshots: + + +Command + + +Output + + +[Back to Table Of Contents](#table-of-contents) + +--- + +
+ +#### Unpinning a patient: `unpin` + +Unpins a pinned patient. + +Format: `unpin PINNEDID` + +- Unpins the patient with the corresponding `PINNEDID` from the **Pinned Patient List**. + +Examples: + +- `unpin 2` Unpins the patient with the `PINNEDID` of **2** from the **Pinned Patient List**. + +[Back to Table Of Contents](#table-of-contents) + +--- + +
+ +### Medical Record Features + +#### Adding a medical record: `addrecord` + +Adds a new medical record to the system. + +Format: `addrecord PATIENTID d/DATETIME c/CONDITIONS... m/MEDICATIONS...` + +- Adds a medical record to the patient with the corresponding `PATIENTID`.
+ +- Duplicate entries for `CONDITIONS` and `MEDICATIONS` are allowed and will be considered as separate entries. + - `addrecord 1 d/10-11-2023 1800 c/Flu c/Flu m/Ibuprofen m/Ibuprofen` will store **[Flu, Flu]** and **[Ibuprofen, Ibuprofen]** for `CONDITIONS` and `MEDICATIONS`, respectively. + +Example: + +- `addrecord 3 d/18-09-2023 1800 c/Fever m/Paracetamol` + Adds a medical record to the patient with the `PATIENTID` of **3**. + The patient visited the clinic on **September 18th, 2023** at **6:00PM** with a **Fever** and was prescribed **Paracetamol**. + +Screenshots: + + +Command + + +Output + + +[Back to Table Of Contents](#table-of-contents) + +--- + +
+ +#### Viewing patient medical records: `view` + +Displays the medical records of a specific patient. + +Format: `view PATIENTID` + +- The medical records of the patient with the corresponding `PATIENTID` will be displayed on screen in the **Medical Record List**.
+ +- The corresponding patient's **Patient Card** will be displayed in the **Patient Being Viewed** section.
Examples: -* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. -* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. -### Locating persons by name: `find` +- `view 2` displays the medical records of the patient with the `PATIENTID` of **2**. The medical records will be displayed in the **Medical Record List** and patient information will be in the **Patient Being Viewed** section. + + + +**Note**: The `PATIENTID` in the **Patient Being Viewed** section will display **1**. If you wish to perform commands on the patient, use the `PATIENTID` displayed in the **Patient List**. + + + +Screenshots: + + +Command + + +Output + + +[Back to Table Of Contents](#table-of-contents) + +--- + +
+ +#### Editing a medical record: `editrecord` + +Edits the details of an existing medical record. + +Format: `editrecord PATIENTID/RECORDID PREFIX/NEWVALUE...` + +- Edits the details of the medical record with the corresponding `RECORDID` of the patient with the corresponding `PATIENTID`.
+ +- Acceptable fields : `DATETIME`, `CONDITION`, `MEDICATION`.
+ +- Existing values in the respective fields will be updated to the `NEWVALUE`.
+ +- Multiple entries of `CONDITION` and `MEDICATION` are allowed i.e `editrecord 1/1 c/Flu c/Fever m/Panadol m/Ibuprofen`.
-Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +**Note**: When editing `CONDITION` and `MEDICATION`, the existing conditions and medications of the record will be removed i.e adding of conditions and medications is not cumulative. -* The search is case-insensitive. e.g `hans` will match `Hans` -* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. -* Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). - e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` + + + + +**Tip**: Spotted an error in the medical record? Fret not, simply use `editrecord` to quickly make changes! + + Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) -### Deleting a person : `delete` +- `editrecord 1/1 d/25-10-2023 1200` Edits the `DATETIME` of the medical record with the `RECORDID` of **1** of the patient with the `PATIENTID` of **1** to **25-10-2023 1200**.
+ +- `editrecord 1/1 c/Headache c/Flu` Edits the `CONDITION` of the medical record with the `RECORDID` of **1** of the patient with the `PATIENTID` of **1** to **Headache, Flu**.
+ +- `editrecord 1/1 c/Headache m/Paracetamol` Edits the `CONDITION` and `MEDICATION` of the medical record with the `RECORDID` of **1** of the patient with the `PATIENTID` of **1** to **Headache** and **Paracetamol**, respectively.
+ +[Back to Table Of Contents](#table-of-contents) + +--- + +
+ +#### Deleting a medical record: `deleterecord` + +Deletes an existing medical record from the system. + +Format: `deleterecord PATIENTID/RECORDID` + +- Deletes the medical record with the corresponding `RECORDID` from the patient with the corresponding `PATIENTID`. + +Example: + +- `deleterecord 2/1` Deletes the medical record with the `RECORDID` of **2** from the patient with the `PATIENTID` of **1**. + +[Back to Table Of Contents](#table-of-contents) + +--- + +#### Searching medical records by keywords: `searchrecord` + +Searches for medical records of the Patient Being Viewed with details containing the corresponding `KEYWORD`. -Deletes the specified person from the address book. +Format: `searchrecord KEYWORD [MOREKEYWORDS]...` -Format: `delete INDEX` +- The search is not case-sensitive. e.g **Penicillin** will match **penicillin**.
-* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. -* The index **must be a positive integer** 1, 2, 3, …​ +- Medical records matching at least one **KEYWORD** will be returned. e.g. **Mild Fever** will return **Mild Flu**, **High Fever**
+ +- Medical records with detail that are not exactly the same as the **KEYWORD** searched will not appear. e.g. **Head** will not return **Headache**.
+ + + + **Important**: The **Patient Being Viewed** section should not be empty. Otherwise `searchrecord` command will not return any medical records. + + Examples: -* `list` followed by `delete 2` deletes the 2nd person in the address book. -* `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command. -### Clearing all entries : `clear` +- `searchrecord Penicillin` Will display medical records of the Patient Being Viewed with **Penicillin** in their details.
+ +- `searchrecord Fever Cough` Will display medical records of the Patient Being Viewed with **Fever** or **Cough** in their details.
+ +- `searchrecord 19-10-2023` Will display medical records of the Patient Being Viewed with **19-10-2023** in their details.
+ +[Back to Table Of Contents](#table-of-contents) + +--- + +
+ +#### Attaching files to a patient's medical record + +Attach local files to a patient's medical record by clicking on the **Attach** button located within each **Record Card**. + + + +**Tip**: A patient's report just comes out? The **Attach File** feature can help you keep documents in an organised manner! + + + + + +**Caution**: + +- Refrain from deleting/relocating the local file. If necessary, re-attach the new file after relocation/deletion. +- Ensure that you have selected a default launcher for the selected file type. + + + +Screenshots: + + +Command + + +Selecting File + + +Output + + +[Back to Table Of Contents](#table-of-contents) + +--- + +
+ +### Appointment Features + +#### Adding an appointment: `addappointment` + +Adds a new appointment to the system. + +Format: `addappointment PATIENTID n/NAME d/DATETIME` + +- Adds an appointment to the patient with the corresponding `PATIENTID`. + +Example: + +- `addappointment 2 n/Eye Examination d/10-10-2023 1800` Adds an `Eye Examination` to the patient with the `PATIENTID` of **2**. The appointment is schedueld for the **10th of October 2023** at **6:00PM**. + +Screenshots: + + +Command + + +Output + + +[Back to Table Of Contents](#table-of-contents) + +--- + +
+ +#### Deleting an appointment: `deleteappointment` + +Deletes an appointment from the system. + +Format: `deleteappointment APPOINTMENTID` -Clears all entries from the address book. +- Deletes the appointment with the corresponding `APPOINTMENTID`. -Format: `clear` + -### Exiting the program : `exit` +**Tip**: A patient cannot attend an appointment? Use `deleteappointment` to remove it from your schedule! + + + +Example: + +- `deleteappointment 1` Deletes the appointment with the `APPOINTMENTID` of **1**. + +[Back to Table Of Contents](#table-of-contents) + +--- + +#### Viewing appointments: `viewappointment` + +Opens/focuses the **Appointment Window**. + +Format: `viewappointment` + + + +**Tip**: Want to know about upcoming appointments? Simply type in `viewappointment`! + + + +[Back to Table Of Contents](#table-of-contents) + +--- + +
+ +### General Features + +#### Viewing help : `help` + +Opens/focuses the **Help Window** displaying all commands. + +Format: `help` + + + +**Tip**: If you get stuck while using MedBook, use the `help` command to get a quick overview of all commands! + + + +[Back to Table Of Contents](#table-of-contents) + +--- + +#### Exiting the program: `exit` Exits the program. Format: `exit` -### Saving the data +[Back to Table Of Contents](#table-of-contents) + +--- + +#### Saving the data -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +MedBook data is saved in the `data` folder automatically after any command that changes the data. This ensures the safety of your data. When MedBook is restarted, the data is loaded back into the application automatically. -### Editing the data file +[Back to Table Of Contents](#table-of-contents) -AddressBook data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +--- + +#### Editing the data file + +MedBook data is saved automatically as a JSON file located in `[jar file location]/data/medbook.json`. The file can be edited directly to make changes to MedBook data. + + + +**Caution**: Only advanced users are encouraged to make changes to the data file as wrong data formatting may result in the application not working as intended. -
:exclamation: **Caution:** -If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it. -
+
-### Archiving data files `[coming in v2.0]` +[Back to Table Of Contents](#table-of-contents) -_Details coming soon ..._ +--- --------------------------------------------------------------------------------------------------------------------- +
## FAQ +**Q**: How do I check my java version?
+**A**: Open a **Terminal** and enter `java -version`. If you do not have Java installed, you can download it [here](https://www.oracle.com/java/technologies/downloads/#java11). + **Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder. +**A**: Install the app in the other computer and copy the `data` folder over to the folder of the installed jar file in the other computer. --------------------------------------------------------------------------------------------------------------------- +[Back to Table Of Contents](#table-of-contents) + +--- ## Known issues 1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again. --------------------------------------------------------------------------------------------------------------------- +[Back to Table Of Contents](#table-of-contents) -## Command summary +--- -Action | Format, Examples ---------|------------------ -**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​`
e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -**Clear** | `clear` -**Delete** | `delete INDEX`
e.g., `delete 3` -**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…​`
e.g.,`edit 2 n/James Lee e/jameslee@example.com` -**Find** | `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake` -**List** | `list` -**Help** | `help` +## Command Summary + +| Action | Format, Examples | +| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Help** | `help` | +| **Add Patient** | `addpatient n/NAME i/NRIC e/EMAIL p/PHONE g/GENDER a/AGE bt/BLOODTYPE [al/ALLERGY]...​`
e.g., `addpatient n/John Doe i/T1234567A e/johndoe@gmail.com p/12345678 g/M a/26 bt/AB+ al/Penicillin` | +| **Add Medical Record** | `addrecord PATIENTID d/DATETIME c/CONDITION... m/MEDICATION...`
e.g., `addrecord 2 d/10-10-2020 1900 c/Fever m/Painkiller` | +| **Add Appointment** | `addappointment PATIENTID n/NAME d/DATETIME`
e.g., `addappointment 2 n/Eye Exam d/10-10-2020 1900` | +| **List** | `list` | +| **View Medical Records** | `view PATIENTID`
e.g., `view 2` | +| **View Appointments** | `viewappointment` | +| **Edit Patient** | `editpatient PATIENTID PREFIX/NEWVALUE...`
e.g.,`editpatient 1 e/johndoe_updated@gmail.com` | +| **Edit Medical Record** | `editrecord PATIENTID/RECORDID PREFIX/NEWVALUE...`
e.g.,`editrecord 1/1 d/25-10-2023 1200` | +| **Search Patients** | `search KEYWORD [MOREKEYWORDS]...`
e.g., `search James` | +| **Search Medical Records** | `searchrecord KEYWORD [MOREKEYWORDS]...`
e.g., `searchrecord Headache` | +| **Delete Patient** | `delete PATIENTID`
e.g., `delete 3` | +| **Delete Medical Record** | `deleterecord PATIENTID/RECORDID`
e.g., `deleterecord 2/1` | +| **Delete Appointment** | `deleteappointment APPOINTMENTID`
e.g., `deleteappointment 1` | +| **Pin Patient** | `pin PATIENTID`
e.g., `pin 2` | +| **Unpin Patient** | `unpin PINNEDID`
e.g. `unpin 2` | +| **Exit** | `exit` | + +[Back to Table Of Contents](#table-of-contents) diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index 6bd245d8f4e..00000000000 --- a/docs/_config.yml +++ /dev/null @@ -1,15 +0,0 @@ -title: "AB-3" -theme: minima - -header_pages: - - UserGuide.md - - DeveloperGuide.md - - AboutUs.md - -markdown: kramdown - -repository: "se-edu/addressbook-level3" -github_icon: "images/github-icon.png" - -plugins: - - jemoji diff --git a/docs/_data/projects.yml b/docs/_data/projects.yml deleted file mode 100644 index 8f3e50cb601..00000000000 --- a/docs/_data/projects.yml +++ /dev/null @@ -1,23 +0,0 @@ -- name: "AB-1" - url: https://se-edu.github.io/addressbook-level1 - -- name: "AB-2" - url: https://se-edu.github.io/addressbook-level2 - -- name: "AB-3" - url: https://se-edu.github.io/addressbook-level3 - -- name: "AB-4" - url: https://se-edu.github.io/addressbook-level4 - -- name: "Duke" - url: https://se-edu.github.io/duke - -- name: "Collate" - url: https://se-edu.github.io/collate - -- name: "Book" - url: https://se-edu.github.io/se-book - -- name: "Resources" - url: https://se-edu.github.io/resources diff --git a/docs/_includes/custom-head.html b/docs/_includes/custom-head.html deleted file mode 100644 index 8559a67ffad..00000000000 --- a/docs/_includes/custom-head.html +++ /dev/null @@ -1,6 +0,0 @@ -{% comment %} - Placeholder to allow defining custom head, in principle, you can add anything here, e.g. favicons: - - 1. Head over to https://realfavicongenerator.net/ to add your own favicons. - 2. Customize default _includes/custom-head.html in your source directory and insert the given code snippet. -{% endcomment %} diff --git a/docs/_includes/head.html b/docs/_includes/head.html deleted file mode 100644 index 83ac5326933..00000000000 --- a/docs/_includes/head.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - {%- include custom-head.html -%} - - {{page.title}} - - diff --git a/docs/_includes/header.html b/docs/_includes/header.html deleted file mode 100644 index 33badcd4f99..00000000000 --- a/docs/_includes/header.html +++ /dev/null @@ -1,36 +0,0 @@ - diff --git a/docs/_layouts/alt-page.html b/docs/_layouts/alt-page.html deleted file mode 100644 index 5dbc6ef245f..00000000000 --- a/docs/_layouts/alt-page.html +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: default ---- -
- -
-

{{ page.alt_title | escape }}

-
- -
- {{ content }} -
- -
diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html deleted file mode 100644 index e092cd572e0..00000000000 --- a/docs/_layouts/default.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - {%- include head.html -%} - - - - {%- include header.html -%} - -
-
- {{ content }} -
-
- - - - diff --git a/docs/_layouts/page.html b/docs/_layouts/page.html deleted file mode 100644 index 01e4b2a93b8..00000000000 --- a/docs/_layouts/page.html +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: default ---- -
- -
-

{{ page.title | escape }}

-
- -
- {{ content }} -
- -
diff --git a/docs/_markbind/layouts/default.md b/docs/_markbind/layouts/default.md new file mode 100644 index 00000000000..7433507c3db --- /dev/null +++ b/docs/_markbind/layouts/default.md @@ -0,0 +1,67 @@ + + + + +
+ + MedBook +
  • User Guide
  • +
  • Developer Guide
  • +
  • About Us
  • +
  • :fab-github: +
  • +
  • + +
  • +
    +
    + +
    + +
    + {{ content }} +
    + + +
    + +
    + +
    + [**Powered by** {{MarkBind}}, generated on {{timestamp}}] +
    +
    diff --git a/docs/_markbind/variables.json b/docs/_markbind/variables.json new file mode 100644 index 00000000000..9d89eb0358b --- /dev/null +++ b/docs/_markbind/variables.json @@ -0,0 +1,3 @@ +{ + "jsonVariableExample": "Your variables can be defined here as well" +} diff --git a/docs/_markbind/variables.md b/docs/_markbind/variables.md new file mode 100644 index 00000000000..89ae5318fa4 --- /dev/null +++ b/docs/_markbind/variables.md @@ -0,0 +1,4 @@ + +To inject this HTML segment in your markbind files, use {{ example }} where you want to place it. +More generally, surround the segment's id with double curly braces. + diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss deleted file mode 100644 index 0d3f6e80ced..00000000000 --- a/docs/_sass/minima/_base.scss +++ /dev/null @@ -1,295 +0,0 @@ -html { - font-size: $base-font-size; -} - -/** - * Reset some basic elements - */ -body, h1, h2, h3, h4, h5, h6, -p, blockquote, pre, hr, -dl, dd, ol, ul, figure { - margin: 0; - padding: 0; - -} - - - -/** - * Basic styling - */ -body { - font: $base-font-weight #{$base-font-size}/#{$base-line-height} $base-font-family; - color: $text-color; - background-color: $background-color; - -webkit-text-size-adjust: 100%; - -webkit-font-feature-settings: "kern" 1; - -moz-font-feature-settings: "kern" 1; - -o-font-feature-settings: "kern" 1; - font-feature-settings: "kern" 1; - font-kerning: normal; - display: flex; - min-height: 100vh; - flex-direction: column; - overflow-wrap: break-word; -} - - - -/** - * Set `margin-bottom` to maintain vertical rhythm - */ -h1, h2, h3, h4, h5, h6, -p, blockquote, pre, -ul, ol, dl, figure, -%vertical-rhythm { - margin-bottom: $spacing-unit / 2; -} - -hr { - margin-top: $spacing-unit; - margin-bottom: $spacing-unit; -} - -/** - * `main` element - */ -main { - display: block; /* Default value of `display` of `main` element is 'inline' in IE 11. */ -} - - - -/** - * Images - */ -img { - max-width: 100%; - vertical-align: middle; -} - - - -/** - * Figures - */ -figure > img { - display: block; -} - -figcaption { - font-size: $small-font-size; -} - - - -/** - * Lists - */ -ul, ol { - margin-left: $spacing-unit; -} - -li { - > ul, - > ol { - margin-bottom: 0; - } -} - - - -/** - * Headings - */ -h1, h2, h3, h4, h5, h6 { - font-weight: $base-font-weight; -} - - - -/** - * Links - */ -a { - color: $link-base-color; - text-decoration: none; - - &:visited { - color: $link-visited-color; - } - - &:hover { - color: $text-color; - text-decoration: underline; - } - - .social-media-list &:hover { - text-decoration: none; - - .username { - text-decoration: underline; - } - } -} - - -/** - * Blockquotes - */ -blockquote { - color: $brand-color; - border-left: 4px solid $brand-color-light; - padding-left: $spacing-unit / 2; - @include relative-font-size(1.125); - font-style: italic; - - > :last-child { - margin-bottom: 0; - } - - i, em { - font-style: normal; - } -} - - - -/** - * Code formatting - */ -pre, -code { - font-family: $code-font-family; - font-size: 0.9375em; - border: 1px solid $brand-color-light; - border-radius: 3px; - background-color: $code-background-color; -} - -code { - padding: 1px 5px; -} - -pre { - padding: 8px 12px; - overflow-x: auto; - - > code { - border: 0; - padding-right: 0; - padding-left: 0; - } -} - -.highlight { - border-radius: 3px; - background: $code-background-color; - @extend %vertical-rhythm; - - .highlighter-rouge & { - background: $code-background-color; - } -} - - - -/** - * Wrapper - */ -.wrapper { - max-width: calc(#{$content-width} - (#{$spacing-unit})); - margin-right: auto; - margin-left: auto; - padding-right: $spacing-unit / 2; - padding-left: $spacing-unit / 2; - @extend %clearfix; - - @media screen and (min-width: $on-large) { - max-width: calc(#{$content-width} - (#{$spacing-unit} * 2)); - padding-right: $spacing-unit; - padding-left: $spacing-unit; - } -} - - - -/** - * Clearfix - */ -%clearfix:after { - content: ""; - display: table; - clear: both; -} - - - -/** - * Icons - */ - -.orange { - color: #f66a0a; -} - -.grey { - color: #828282; -} - -/** - * Tables - */ -table { - margin-bottom: $spacing-unit; - width: 100%; - text-align: $table-text-align; - color: $table-text-color; - border-collapse: collapse; - border: 1px solid $table-border-color; - tr { - &:nth-child(even) { - background-color: $table-zebra-color; - } - } - th, td { - padding: ($spacing-unit / 3) ($spacing-unit / 2); - } - th { - background-color: $table-header-bg-color; - border: 1px solid $table-header-border; - } - td { - border: 1px solid $table-border-color; - } - - @include media-query($on-laptop) { - display: block; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - -ms-overflow-style: -ms-autohiding-scrollbar; - } -} - -@media print { - /** - * Prevents page break from cutting through content when printing - */ - body { - display: block; - } - /** - * Replaces the top navigation menu with the project name when printing - */ - .site-header .wrapper { - display: none; - } - .site-header { - text-align: center; - } - .site-header:before { - content: "AB-3"; - font-size: 32px; - } -} - diff --git a/docs/_sass/minima/_layout.scss b/docs/_sass/minima/_layout.scss deleted file mode 100644 index ca99f981701..00000000000 --- a/docs/_sass/minima/_layout.scss +++ /dev/null @@ -1,263 +0,0 @@ -/** - * Site header - */ -.site-header { - border-top: 5px solid $brand-color-dark; - border-bottom: 1px solid $brand-color-light; - min-height: $spacing-unit * 1.865; - line-height: $base-line-height * $base-font-size * 2.25; - - // Positioning context for the mobile navigation icon - position: relative; -} - -.site-title { - @include relative-font-size(1.625); - font-weight: 300; - letter-spacing: -1px; - margin-bottom: 0; - float: left; - - @include media-query($on-palm) { - padding-right: 45px; - } - - &, - &:visited { - color: $brand-color-dark; - } -} - -.site-nav { - position: absolute; - top: 9px; - right: $spacing-unit / 2; - background-color: $background-color; - border: 1px solid $brand-color-light; - border-radius: 5px; - text-align: right; - - .nav-trigger { - display: none; - } - - .menu-icon { - float: right; - width: 36px; - height: 26px; - line-height: 0; - padding-top: 10px; - text-align: center; - - > svg path { - fill: $brand-color-dark; - } - } - - label[for="nav-trigger"] { - display: block; - float: right; - width: 36px; - height: 36px; - z-index: 2; - cursor: pointer; - } - - input ~ .trigger { - clear: both; - display: none; - } - - input:checked ~ .trigger { - display: block; - padding-bottom: 5px; - } - - .page-link { - color: $text-color; - line-height: $base-line-height; - display: block; - padding: 5px 10px; - - // Gaps between nav items, but not on the last one - &:not(:last-child) { - margin-right: 0; - } - margin-left: 20px; - } - - @media screen and (min-width: $on-medium) { - position: static; - float: right; - border: none; - background-color: inherit; - - label[for="nav-trigger"] { - display: none; - } - - .menu-icon { - display: none; - } - - input ~ .trigger { - display: block; - } - - .page-link { - display: inline; - padding: 0; - - &:not(:last-child) { - margin-right: 20px; - } - margin-left: auto; - } - } -} - - - -/** - * Page content - */ -.page-content { - padding: $spacing-unit 0; - flex: 1 0 auto; -} - -.page-heading { - @include relative-font-size(2); -} - -.post-list-heading { - @include relative-font-size(1.75); -} - -.post-list { - margin-left: 0; - list-style: none; - - > li { - margin-bottom: $spacing-unit; - } -} - -.post-meta { - font-size: $small-font-size; - color: $brand-color; -} - -.post-link { - display: block; - @include relative-font-size(1.5); -} - - - -/** - * Posts - */ -.post-header { - margin-bottom: $spacing-unit; -} - -.post-title, -.post-content h1 { - @include relative-font-size(2.625); - letter-spacing: -1px; - line-height: 1.15; - - @media screen and (min-width: $on-large) { - @include relative-font-size(2.625); - } -} - -.post-content { - margin-bottom: $spacing-unit; - - h1, h2, h3 { margin-top: $spacing-unit * 2 } - h4, h5, h6 { margin-top: $spacing-unit } - - h2 { - @include relative-font-size(1.75); - - @media screen and (min-width: $on-large) { - @include relative-font-size(2); - } - } - - h3 { - @include relative-font-size(1.375); - - @media screen and (min-width: $on-large) { - @include relative-font-size(1.625); - } - } - - h4 { - @include relative-font-size(1.25); - } - - h5 { - @include relative-font-size(1.125); - } - h6 { - @include relative-font-size(1.0625); - } -} - - -.social-media-list { - display: table; - margin: 0 auto; - li { - float: left; - margin: 5px 10px 5px 0; - &:last-of-type { margin-right: 0 } - a { - display: block; - padding: $spacing-unit / 4; - border: 1px solid $brand-color-light; - &:hover { border-color: darken($brand-color-light, 10%) } - } - } -} - - - -/** - * Pagination navbar - */ -.pagination { - margin-bottom: $spacing-unit; - @extend .social-media-list; - li { - a, div { - min-width: 41px; - text-align: center; - box-sizing: border-box; - } - div { - display: block; - padding: $spacing-unit / 4; - border: 1px solid transparent; - - &.pager-edge { - color: darken($brand-color-light, 5%); - border: 1px dashed; - } - } - } -} - - - -/** - * Grid helpers - */ -@media screen and (min-width: $on-large) { - .one-half { - width: calc(50% - (#{$spacing-unit} / 2)); - } -} diff --git a/docs/_sass/minima/custom-mixins.scss b/docs/_sass/minima/custom-mixins.scss deleted file mode 100644 index 9d4bedc1c67..00000000000 --- a/docs/_sass/minima/custom-mixins.scss +++ /dev/null @@ -1,21 +0,0 @@ -@mixin alert-variant($background, $border, $color) { - color: $color; - @include gradient-bg($background); - border-color: $border; - - .alert-link { - color: darken($color, 10%); - } -} - -@mixin gradient-bg($color, $foreground: null) { - @if $enable-gradients { - @if $foreground { - background-image: $foreground, linear-gradient(180deg, mix($body-bg, $color, 15%), $color); - } @else { - background-image: linear-gradient(180deg, mix($body-bg, $color, 15%), $color); - } - } @else { - background-color: $color; - } -} diff --git a/docs/_sass/minima/custom-styles.scss b/docs/_sass/minima/custom-styles.scss deleted file mode 100644 index 56b5d56b430..00000000000 --- a/docs/_sass/minima/custom-styles.scss +++ /dev/null @@ -1,34 +0,0 @@ -// Placeholder to allow defining custom styles that override everything else. -// (Use `_sass/minima/custom-variables.scss` to override variable defaults) -h2, h3, h4, h5, h6 { - color: #e46c0a; -} - -// Bootstrap style alerts -.alert { - position: relative; - padding: $alert-padding-y $alert-padding-x; - margin-bottom: $alert-margin-bottom; - border: $alert-border-width solid transparent; - border-radius : $alert-border-radius; -} - -// Headings for larger alerts -.alert-heading { - // Specified to prevent conflicts of changing $headings-color - color: inherit; -} - -// Provide class for links that match alerts -.alert-link { - font-weight: $alert-link-font-weight; -} - -// Generate contextual modifier classes for colorizing the alert. - -@each $color, $value in $theme-colors { - .alert-#{$color} { - @include alert-variant(color-level($value, $alert-bg-level), color-level($value, $alert-border-level), color-level($value, $alert-color-level)); - } -} - diff --git a/docs/_sass/minima/custom-variables.scss b/docs/_sass/minima/custom-variables.scss deleted file mode 100644 index a128970cbe7..00000000000 --- a/docs/_sass/minima/custom-variables.scss +++ /dev/null @@ -1,76 +0,0 @@ -// Placeholder to allow overriding predefined variables smoothly. - -//Bootstrap's default -$white: #fff !default; -$gray-100: #f8f9fa !default; -$gray-200: #e9ecef !default; -$gray-300: #dee2e6 !default; -$gray-400: #ced4da !default; -$gray-500: #adb5bd !default; -$gray-600: #6c757d !default; -$gray-700: #495057 !default; -$gray-800: #343a40 !default; -$gray-900: #212529 !default; -$black: #000 !default; -$blue: #0d6efd !default; -$indigo: #6610f2 !default; -$purple: #6f42c1 !default; -$pink: #d63384 !default; -$red: #dc3545 !default; -$orange: #fd7e14 !default; -$yellow: #ffc107 !default; -$green: #28a745 !default; -$teal: #20c997 !default; -$cyan: #17a2b8 !default; - -$primary: $blue !default; -$secondary: $gray-600 !default; -$success: $green !default; -$info: $cyan !default; -$warning: $yellow !default; -$danger: $red !default; -$light: $gray-100 !default; -$dark: $gray-800 !default; - -$theme-colors: ( - "primary": $primary, - "secondary": $secondary, - "success": $success, - "info": $info, - "warning": $warning, - "danger": $danger, - "light": $light, - "dark": $dark -) !default; - -$theme-color-interval: 8% !default; - -$body-bg: $white !default; -$body-color: $gray-900 !default; -$body-text-align: null !default; - -$enable-gradients: true; - -// Define alert colors, border radius, and padding. -$border-radius: .25rem !default; -$border-width: 1px !default; -$font-weight-bold: 700 !default; - -$alert-padding-y: .75rem !default; -$alert-padding-x: 1.25rem !default; -$alert-margin-bottom: 1rem !default; -$alert-border-radius: $border-radius !default; -$alert-link-font-weight: $font-weight-bold !default; -$alert-border-width: $border-width !default; - -$alert-bg-level: -10 !default; -$alert-border-level: -9 !default; -$alert-color-level: 6 !default; - -// Request a color level -// scss-docs-start color-level -@function color-level($color: $primary, $level: 0) { - $color-base: if($level > 0, $black, $white); - $level: abs($level); - @return mix($color-base, $color, $level * $theme-color-interval); -} diff --git a/docs/_sass/minima/initialize.scss b/docs/_sass/minima/initialize.scss deleted file mode 100644 index 30288811151..00000000000 --- a/docs/_sass/minima/initialize.scss +++ /dev/null @@ -1,51 +0,0 @@ -@charset "utf-8"; - -// Define defaults for each variable. - -$base-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Segoe UI Symbol", "Segoe UI Emoji", "Apple Color Emoji", Roboto, Helvetica, Arial, sans-serif !default; -$code-font-family: "Menlo", "Inconsolata", "Consolas", "Roboto Mono", "Ubuntu Mono", "Liberation Mono", "Courier New", monospace; -$base-font-size: 16px !default; -$base-font-weight: 400 !default; -$small-font-size: $base-font-size * 0.875 !default; -$base-line-height: 1.5 !default; - -$spacing-unit: 30px !default; - -$table-text-align: left !default; - -// Width of the content area -$content-width: 800px !default; - -$on-palm: 600px !default; -$on-laptop: 800px !default; - -$on-medium: $on-palm !default; -$on-large: $on-laptop !default; - -// Use media queries like this: -// @include media-query($on-palm) { -// .wrapper { -// padding-right: $spacing-unit / 2; -// padding-left: $spacing-unit / 2; -// } -// } -// Notice the following mixin uses max-width, in a deprecated, desktop-first -// approach, whereas media queries used elsewhere now use min-width. -@mixin media-query($device) { - @media screen and (max-width: $device) { - @content; - } -} - -@mixin relative-font-size($ratio) { - font-size: #{$ratio}rem; -} - -// Import pre-styling-overrides hook and style-partials. -@import - "minima/custom-variables", // Hook to override predefined variables. - "minima/custom-mixins", // Hook to add custom mixins. - "minima/base", // Defines element resets. - "minima/layout", // Defines structure and style based on CSS selectors. - "minima/custom-styles" // Hook to override existing styles. -; diff --git a/docs/_sass/minima/skins/classic.scss b/docs/_sass/minima/skins/classic.scss deleted file mode 100644 index 37ea9c5244c..00000000000 --- a/docs/_sass/minima/skins/classic.scss +++ /dev/null @@ -1,84 +0,0 @@ -@charset "utf-8"; - -$brand-color: #828282 !default; -$brand-color-light: lighten($brand-color, 40%) !default; -$brand-color-dark: darken($brand-color, 25%) !default; - -$text-color: #111 !default; -$background-color: #fdfdfd !default; -$code-background-color: #eef !default; - -$link-base-color: #2a7ae2 !default; -$link-visited-color: darken($link-base-color, 15%) !default; - -$table-text-color: lighten($text-color, 18%) !default; -$table-zebra-color: lighten($brand-color, 46%) !default; -$table-header-bg-color: lighten($brand-color, 43%) !default; -$table-header-border: lighten($brand-color, 36%) !default; -$table-border-color: $brand-color-light !default; - - -// Syntax highlighting styles should be adjusted appropriately for every "skin" -// ---------------------------------------------------------------------------- - -.highlight { - .c { color: #998; font-style: italic } // Comment - .err { color: #a61717; background-color: #e3d2d2 } // Error - .k { font-weight: bold } // Keyword - .o { font-weight: bold } // Operator - .cm { color: #998; font-style: italic } // Comment.Multiline - .cp { color: #999; font-weight: bold } // Comment.Preproc - .c1 { color: #998; font-style: italic } // Comment.Single - .cs { color: #999; font-weight: bold; font-style: italic } // Comment.Special - .gd { color: #000; background-color: #fdd } // Generic.Deleted - .gd .x { color: #000; background-color: #faa } // Generic.Deleted.Specific - .ge { font-style: italic } // Generic.Emph - .gr { color: #a00 } // Generic.Error - .gh { color: #999 } // Generic.Heading - .gi { color: #000; background-color: #dfd } // Generic.Inserted - .gi .x { color: #000; background-color: #afa } // Generic.Inserted.Specific - .go { color: #888 } // Generic.Output - .gp { color: #555 } // Generic.Prompt - .gs { font-weight: bold } // Generic.Strong - .gu { color: #aaa } // Generic.Subheading - .gt { color: #a00 } // Generic.Traceback - .kc { font-weight: bold } // Keyword.Constant - .kd { font-weight: bold } // Keyword.Declaration - .kp { font-weight: bold } // Keyword.Pseudo - .kr { font-weight: bold } // Keyword.Reserved - .kt { color: #458; font-weight: bold } // Keyword.Type - .m { color: #099 } // Literal.Number - .s { color: #d14 } // Literal.String - .na { color: #008080 } // Name.Attribute - .nb { color: #0086B3 } // Name.Builtin - .nc { color: #458; font-weight: bold } // Name.Class - .no { color: #008080 } // Name.Constant - .ni { color: #800080 } // Name.Entity - .ne { color: #900; font-weight: bold } // Name.Exception - .nf { color: #900; font-weight: bold } // Name.Function - .nn { color: #555 } // Name.Namespace - .nt { color: #000080 } // Name.Tag - .nv { color: #008080 } // Name.Variable - .ow { font-weight: bold } // Operator.Word - .w { color: #bbb } // Text.Whitespace - .mf { color: #099 } // Literal.Number.Float - .mh { color: #099 } // Literal.Number.Hex - .mi { color: #099 } // Literal.Number.Integer - .mo { color: #099 } // Literal.Number.Oct - .sb { color: #d14 } // Literal.String.Backtick - .sc { color: #d14 } // Literal.String.Char - .sd { color: #d14 } // Literal.String.Doc - .s2 { color: #d14 } // Literal.String.Double - .se { color: #d14 } // Literal.String.Escape - .sh { color: #d14 } // Literal.String.Heredoc - .si { color: #d14 } // Literal.String.Interpol - .sx { color: #d14 } // Literal.String.Other - .sr { color: #009926 } // Literal.String.Regex - .s1 { color: #d14 } // Literal.String.Single - .ss { color: #990073 } // Literal.String.Symbol - .bp { color: #999 } // Name.Builtin.Pseudo - .vc { color: #008080 } // Name.Variable.Class - .vg { color: #008080 } // Name.Variable.Global - .vi { color: #008080 } // Name.Variable.Instance - .il { color: #099 } // Literal.Number.Integer.Long -} diff --git a/docs/_sass/minima/skins/solarized-dark.scss b/docs/_sass/minima/skins/solarized-dark.scss deleted file mode 100644 index f3b1f387de0..00000000000 --- a/docs/_sass/minima/skins/solarized-dark.scss +++ /dev/null @@ -1,4 +0,0 @@ -@charset "utf-8"; - -$sol-is-dark: true; -@import "minima/skins/solarized"; diff --git a/docs/_sass/minima/skins/solarized.scss b/docs/_sass/minima/skins/solarized.scss deleted file mode 100644 index 982bd7f2990..00000000000 --- a/docs/_sass/minima/skins/solarized.scss +++ /dev/null @@ -1,133 +0,0 @@ -@charset "utf-8"; - -// Solarized skin -// ============== -// Created by Sander Voerman using the Solarized -// color scheme by Ethan Schoonover . - -// This style sheet implements two options for the minima.skin setting: -// "solarized" for light mode and "solarized-dark" for dark mode. -$sol-is-dark: false !default; - - -// Color scheme -// ------------ -// The inline comments show the canonical L*a*b values for each color. - -$sol-base03: #002b36; // 15 -12 -12 -$sol-base02: #073642; // 20 -12 -12 -$sol-base01: #586e75; // 45 -07 -07 -$sol-base00: #657b83; // 50 -07 -07 -$sol-base0: #839496; // 60 -06 -03 -$sol-base1: #93a1a1; // 65 -05 -02 -$sol-base2: #eee8d5; // 92 -00 10 -$sol-base3: #fdf6e3; // 97 00 10 -$sol-yellow: #b58900; // 60 10 65 -$sol-orange: #cb4b16; // 50 50 55 -$sol-red: #dc322f; // 50 65 45 -$sol-magenta: #d33682; // 50 65 -05 -$sol-violet: #6c71c4; // 50 15 -45 -$sol-blue: #268bd2; // 55 -10 -45 -$sol-cyan: #2aa198; // 60 -35 -05 -$sol-green: #859900; // 60 -20 65 - -$sol-mono3: $sol-base3; -$sol-mono2: $sol-base2; -$sol-mono1: $sol-base1; -$sol-mono00: $sol-base00; -$sol-mono01: $sol-base01; - -@if $sol-is-dark { - $sol-mono3: $sol-base03; - $sol-mono2: $sol-base02; - $sol-mono1: $sol-base01; - $sol-mono00: $sol-base0; - $sol-mono01: $sol-base1; -} - - -// Minima color variables -// ---------------------- - -$brand-color: $sol-mono1 !default; -$brand-color-light: mix($sol-mono1, $sol-mono3) !default; -$brand-color-dark: $sol-mono00 !default; - -$text-color: $sol-mono01 !default; -$background-color: $sol-mono3 !default; -$code-background-color: $sol-mono2 !default; - -$link-base-color: $sol-blue !default; -$link-visited-color: mix($sol-blue, $sol-mono00) !default; - -$table-text-color: $sol-mono00 !default; -$table-zebra-color: mix($sol-mono2, $sol-mono3) !default; -$table-header-bg-color: $sol-mono2 !default; -$table-header-border: $sol-mono1 !default; -$table-border-color: $sol-mono1 !default; - - -// Syntax highlighting styles -// -------------------------- - -.highlight { - .c { color: $sol-mono1; font-style: italic } // Comment - .err { color: $sol-red } // Error - .k { color: $sol-mono01; font-weight: bold } // Keyword - .o { color: $sol-mono01; font-weight: bold } // Operator - .cm { color: $sol-mono1; font-style: italic } // Comment.Multiline - .cp { color: $sol-mono1; font-weight: bold } // Comment.Preproc - .c1 { color: $sol-mono1; font-style: italic } // Comment.Single - .cs { color: $sol-mono1; font-weight: bold; font-style: italic } // Comment.Special - .gd { color: $sol-red } // Generic.Deleted - .gd .x { color: $sol-red } // Generic.Deleted.Specific - .ge { color: $sol-mono00; font-style: italic } // Generic.Emph - .gr { color: $sol-red } // Generic.Error - .gh { color: $sol-mono1 } // Generic.Heading - .gi { color: $sol-green } // Generic.Inserted - .gi .x { color: $sol-green } // Generic.Inserted.Specific - .go { color: $sol-mono00 } // Generic.Output - .gp { color: $sol-mono00 } // Generic.Prompt - .gs { color: $sol-mono01; font-weight: bold } // Generic.Strong - .gu { color: $sol-mono1 } // Generic.Subheading - .gt { color: $sol-red } // Generic.Traceback - .kc { color: $sol-mono01; font-weight: bold } // Keyword.Constant - .kd { color: $sol-mono01; font-weight: bold } // Keyword.Declaration - .kp { color: $sol-mono01; font-weight: bold } // Keyword.Pseudo - .kr { color: $sol-mono01; font-weight: bold } // Keyword.Reserved - .kt { color: $sol-violet; font-weight: bold } // Keyword.Type - .m { color: $sol-cyan } // Literal.Number - .s { color: $sol-magenta } // Literal.String - .na { color: $sol-cyan } // Name.Attribute - .nb { color: $sol-blue } // Name.Builtin - .nc { color: $sol-violet; font-weight: bold } // Name.Class - .no { color: $sol-cyan } // Name.Constant - .ni { color: $sol-violet } // Name.Entity - .ne { color: $sol-violet; font-weight: bold } // Name.Exception - .nf { color: $sol-blue; font-weight: bold } // Name.Function - .nn { color: $sol-mono00 } // Name.Namespace - .nt { color: $sol-blue } // Name.Tag - .nv { color: $sol-cyan } // Name.Variable - .ow { color: $sol-mono01; font-weight: bold } // Operator.Word - .w { color: $sol-mono1 } // Text.Whitespace - .mf { color: $sol-cyan } // Literal.Number.Float - .mh { color: $sol-cyan } // Literal.Number.Hex - .mi { color: $sol-cyan } // Literal.Number.Integer - .mo { color: $sol-cyan } // Literal.Number.Oct - .sb { color: $sol-magenta } // Literal.String.Backtick - .sc { color: $sol-magenta } // Literal.String.Char - .sd { color: $sol-magenta } // Literal.String.Doc - .s2 { color: $sol-magenta } // Literal.String.Double - .se { color: $sol-magenta } // Literal.String.Escape - .sh { color: $sol-magenta } // Literal.String.Heredoc - .si { color: $sol-magenta } // Literal.String.Interpol - .sx { color: $sol-magenta } // Literal.String.Other - .sr { color: $sol-green } // Literal.String.Regex - .s1 { color: $sol-magenta } // Literal.String.Single - .ss { color: $sol-magenta } // Literal.String.Symbol - .bp { color: $sol-mono1 } // Name.Builtin.Pseudo - .vc { color: $sol-cyan } // Name.Variable.Class - .vg { color: $sol-cyan } // Name.Variable.Global - .vi { color: $sol-cyan } // Name.Variable.Instance - .il { color: $sol-cyan } // Literal.Number.Integer.Long -} diff --git a/docs/assets/css/style.scss b/docs/assets/css/style.scss deleted file mode 100644 index b5ec6976efa..00000000000 --- a/docs/assets/css/style.scss +++ /dev/null @@ -1,12 +0,0 @@ ---- -# Only the main Sass file needs front matter (the dashes are enough) ---- - -@import - "minima/skins/{{ site.minima.skin | default: 'classic' }}", - "minima/initialize"; - -.icon { - height: 21px; - width: 21px -} diff --git a/docs/diagrams/AddAppointmentSequenceDiagram.puml b/docs/diagrams/AddAppointmentSequenceDiagram.puml new file mode 100644 index 00000000000..ae5bd87494c --- /dev/null +++ b/docs/diagrams/AddAppointmentSequenceDiagram.puml @@ -0,0 +1,76 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":AddAppointmentCommandParser" as AddAppointmentCommandParser LOGIC_COLOR +participant "a:AddAppointmentCommand" as AddAppointmentCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("addappointment 1 n/Eye Exam...") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("addappointment 1 n/Eye Exam...") +activate AddressBookParser + +create AddAppointmentCommandParser +AddressBookParser -> AddAppointmentCommandParser +activate AddAppointmentCommandParser + +AddAppointmentCommandParser --> AddressBookParser +deactivate AddAppointmentCommandParser + +AddressBookParser -> AddAppointmentCommandParser : parse("1 n/Eye Exam...") +activate AddAppointmentCommandParser + +create AddAppointmentCommand +AddAppointmentCommandParser -> AddAppointmentCommand +activate AddAppointmentCommand + +AddAppointmentCommand --> AddAppointmentCommandParser : a +deactivate AddAppointmentCommand + +AddAppointmentCommandParser --> AddressBookParser : a +deactivate AddAppointmentCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +AddAppointmentCommandParser -[hidden]-> AddressBookParser +deactivate AddAppointmentCommandParser + +AddressBookParser --> LogicManager : a +deactivate AddressBookParser + +LogicManager -> AddAppointmentCommand : execute(model) +activate AddAppointmentCommand + +AddAppointmentCommand -> Model : setPerson(targetPerson, newPerson) +activate Model + +Model --> AddAppointmentCommand +deactivate Model + +AddAppointmentCommand -> Model : resetAppointmentList() +activate Model + +Model --> AddAppointmentCommand +deactivate Model + +create CommandResult +AddAppointmentCommand -> CommandResult +activate CommandResult + +CommandResult --> AddAppointmentCommand +deactivate CommandResult + +AddAppointmentCommand --> LogicManager : result +deactivate AddAppointmentCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/AddPatientSequenceDiagram.puml b/docs/diagrams/AddPatientSequenceDiagram.puml new file mode 100644 index 00000000000..537e12ccbfa --- /dev/null +++ b/docs/diagrams/AddPatientSequenceDiagram.puml @@ -0,0 +1,71 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":AddCommandParser" as AddCommandParser LOGIC_COLOR +participant "a:AddCommand" as AddCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("addpatient n/Alice Pauline...") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("addpatient n/Alice Pauline...") +activate AddressBookParser + +create AddCommandParser +AddressBookParser -> AddCommandParser +activate AddCommandParser + +AddCommandParser --> AddressBookParser +deactivate AddCommandParser + +AddressBookParser -> AddCommandParser : parse("n/Alice Pauline...") +activate AddCommandParser + +create AddCommand +AddCommandParser -> AddCommand +activate AddCommand + +AddCommand --> AddCommandParser : a +deactivate AddCommand + +AddCommandParser --> AddressBookParser : a +deactivate AddCommand +'Hidden arrow to position the destroy marker below the end of the activation bar. +AddCommandParser -[hidden]-> AddressBookParser +deactivate AddCommandParser + +AddressBookParser --> LogicManager : a +deactivate AddressBookParser + +LogicManager -> AddCommand : execute(model) +activate AddCommand + +AddCommand -> Model : addPerson(newPerson) +activate Model + +Model --> AddCommand +deactivate Model + +create CommandResult +AddCommand -> CommandResult +activate CommandResult + +CommandResult --> AddCommand +deactivate CommandResult + +AddCommand --> LogicManager : result +deactivate AddCommand + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/AddRecordSequenceDiagram.puml b/docs/diagrams/AddRecordSequenceDiagram.puml new file mode 100644 index 00000000000..79e26068de3 --- /dev/null +++ b/docs/diagrams/AddRecordSequenceDiagram.puml @@ -0,0 +1,74 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":AddRecordCommandParser" as AddRecordCommandParser LOGIC_COLOR +participant "a:AddRecordCommand" as AddRecordCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("addrecord 1 d/27-10-2023...") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("addrecord 1 d/27-10-2023...") +activate AddressBookParser + +create AddRecordCommandParser +AddressBookParser -> AddRecordCommandParser +activate AddRecordCommandParser + +AddRecordCommandParser --> AddressBookParser +deactivate AddRecordCommandParser + +AddressBookParser -> AddRecordCommandParser : parse("1 d/27-10-2023...) +activate AddRecordCommandParser + +create AddRecordCommand +AddRecordCommandParser -> AddRecordCommand +activate AddRecordCommand + +AddRecordCommand --> AddRecordCommandParser : a +deactivate AddRecordCommand + +AddRecordCommandParser --> AddressBookParser : a +deactivate AddRecordCommandParser + +AddressBookParser --> LogicManager : a +deactivate AddressBookParser + +LogicManager -> AddRecordCommand : execute(model) +activate AddRecordCommand + +AddRecordCommand -> Model : setPerson(targetPerson, newPerson) +activate Model + +Model --> AddRecordCommand +deactivate Model + +AddRecordCommand -> Model : updateRecordList(newPerson) +activate Model + +Model --> AddRecordCommand +deactivate Model + +create CommandResult +AddRecordCommand -> CommandResult +activate CommandResult + +CommandResult --> AddRecordCommand +deactivate CommandResult + +AddRecordCommand --> LogicManager : result +deactivate AddRecordCommand + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/AppointmentClassDiagram.puml b/docs/diagrams/AppointmentClassDiagram.puml new file mode 100644 index 00000000000..5a0a583c628 --- /dev/null +++ b/docs/diagrams/AppointmentClassDiagram.puml @@ -0,0 +1,11 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +UniqueAppointmentList -down-> "*" Appointment +Appointment *--> "1" DateTime +Appointment *--> "1" Nric +Appointment *--> "1" Name +@enduml diff --git a/docs/diagrams/DeleteAppointmentSequenceDiagram.puml b/docs/diagrams/DeleteAppointmentSequenceDiagram.puml new file mode 100644 index 00000000000..4c0a760161e --- /dev/null +++ b/docs/diagrams/DeleteAppointmentSequenceDiagram.puml @@ -0,0 +1,77 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":DeleteAppointmentCommandParser" as DeleteAppointmentCommandParser LOGIC_COLOR +participant "c:DeleteAppointmentCommand" as DeleteAppointmentCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("deleteappointment 1") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("deleteappointment 1") +activate AddressBookParser + +create DeleteAppointmentCommandParser +AddressBookParser -> DeleteAppointmentCommandParser +activate DeleteAppointmentCommandParser + +DeleteAppointmentCommandParser --> AddressBookParser +deactivate DeleteAppointmentCommandParser + +AddressBookParser -> DeleteAppointmentCommandParser : parse("1") +activate DeleteAppointmentCommandParser + +create DeleteAppointmentCommand +DeleteAppointmentCommandParser -> DeleteAppointmentCommand +activate DeleteAppointmentCommand + +DeleteAppointmentCommand --> DeleteAppointmentCommandParser : c +deactivate DeleteAppointmentCommand + +DeleteAppointmentCommandParser --> AddressBookParser : c +deactivate DeleteAppointmentCommand +'Hidden arrow to position the destroy marker below the end of the activation bar. +DeleteAppointmentCommandParser -[hidden]-> AddressBookParser +deactivate DeleteAppointmentCommandParser + +AddressBookParser --> LogicManager : c +deactivate AddressBookParser + +LogicManager -> DeleteAppointmentCommand : execute(model) +activate DeleteAppointmentCommand + +DeleteAppointmentCommand -> Model : setPerson(targetPerson, newPerson) +activate Model + +Model --> DeleteAppointmentCommand +deactivate Model + +DeleteAppointmentCommand -> Model : resetAppointmentList() +activate Model + +Model --> DeleteAppointmentCommand +deactivate Model + +create CommandResult +DeleteAppointmentCommand -> CommandResult +activate CommandResult + +CommandResult --> DeleteAppointmentCommand +deactivate CommandResult + +DeleteAppointmentCommand --> LogicManager : result +deactivate DeleteAppointmentCommand + + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/DeletePatientSequenceDiagram.puml b/docs/diagrams/DeletePatientSequenceDiagram.puml new file mode 100644 index 00000000000..7d3536806e8 --- /dev/null +++ b/docs/diagrams/DeletePatientSequenceDiagram.puml @@ -0,0 +1,71 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR +participant "c:DeleteCommand" as DeleteCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("delete 1") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("delete 1") +activate AddressBookParser + +create DeleteCommandParser +AddressBookParser -> DeleteCommandParser +activate DeleteCommandParser + +DeleteCommandParser --> AddressBookParser +deactivate DeleteCommandParser + +AddressBookParser -> DeleteCommandParser : parse("1") +activate DeleteCommandParser + +create DeleteCommand +DeleteCommandParser -> DeleteCommand +activate DeleteCommand + +DeleteCommand --> DeleteCommandParser : c +deactivate DeleteCommand + +DeleteCommandParser --> AddressBookParser : c +deactivate DeleteCommand +'Hidden arrow to position the destroy marker below the end of the activation bar. +DeleteCommandParser -[hidden]-> AddressBookParser +deactivate DeleteCommandParser + +AddressBookParser --> LogicManager : c +deactivate AddressBookParser + +LogicManager -> DeleteCommand : execute(model) +activate DeleteCommand + +DeleteCommand -> Model : deletePerson(targetPerson) +activate Model + +Model --> DeleteCommand +deactivate Model + +create CommandResult +DeleteCommand -> CommandResult +activate CommandResult + +CommandResult --> DeleteCommand +deactivate CommandResult + +DeleteCommand --> LogicManager : result +deactivate DeleteCommand + + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/DeleteRecordSequenceDiagram.puml b/docs/diagrams/DeleteRecordSequenceDiagram.puml new file mode 100644 index 00000000000..c1f203e256c --- /dev/null +++ b/docs/diagrams/DeleteRecordSequenceDiagram.puml @@ -0,0 +1,77 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":DeleteRecordCommandParser" as DeleteRecordCommandParser LOGIC_COLOR +participant "c:DeleteRecordCommand" as DeleteRecordCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("deleterecord 1/1") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("deleterecord 1/1") +activate AddressBookParser + +create DeleteRecordCommandParser +AddressBookParser -> DeleteRecordCommandParser +activate DeleteRecordCommandParser + +DeleteRecordCommandParser --> AddressBookParser +deactivate DeleteRecordCommandParser + +AddressBookParser -> DeleteRecordCommandParser : parse("1/1") +activate DeleteRecordCommandParser + +create DeleteRecordCommand +DeleteRecordCommandParser -> DeleteRecordCommand +activate DeleteRecordCommand + +DeleteRecordCommand --> DeleteRecordCommandParser : c +deactivate DeleteRecordCommand + +DeleteRecordCommandParser --> AddressBookParser : c +deactivate DeleteRecordCommand +'Hidden arrow to position the destroy marker below the end of the activation bar. +DeleteRecordCommandParser -[hidden]-> AddressBookParser +deactivate DeleteRecordCommandParser + +AddressBookParser --> LogicManager : c +deactivate AddressBookParser + +LogicManager -> DeleteRecordCommand : execute(model) +activate DeleteRecordCommand + +DeleteRecordCommand -> Model : setPerson(targetPerson, newPerson) +activate Model + +Model --> DeleteRecordCommand +deactivate Model + +DeleteRecordCommand -> Model : updateRecordList(newPerson) +activate Model + +Model --> DeleteRecordCommand +deactivate Model + +create CommandResult +DeleteRecordCommand -> CommandResult +activate CommandResult + +CommandResult --> DeleteRecordCommand +deactivate CommandResult + +DeleteRecordCommand --> LogicManager : result +deactivate DeleteRecordCommand + + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 40ea6c9dc4c..cd39c4a12fd 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -41,12 +41,12 @@ DeleteCommandParser --> AddressBookParser : d deactivate DeleteCommandParser 'Hidden arrow to position the destroy marker below the end of the activation bar. DeleteCommandParser -[hidden]-> AddressBookParser -destroy DeleteCommandParser +deactivate DeleteCommandParser AddressBookParser --> LogicManager : d deactivate AddressBookParser -LogicManager -> DeleteCommand : execute() +LogicManager -> DeleteCommand : execute(model) activate DeleteCommand DeleteCommand -> Model : deletePerson(1) diff --git a/docs/diagrams/EditPatientSequenceDiagram.puml b/docs/diagrams/EditPatientSequenceDiagram.puml new file mode 100644 index 00000000000..d6951cffc99 --- /dev/null +++ b/docs/diagrams/EditPatientSequenceDiagram.puml @@ -0,0 +1,79 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":EditCommandParser" as EditCommandParser LOGIC_COLOR +participant "e:EditCommand" as EditCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "model:Model" as Model MODEL_COLOR +participant "editedPerson:Person" as Person MODEL_COLOR +end box + +[-> LogicManager : execute("editpatient 1 n/Bob") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("editpatient 1 n/Bob") +activate AddressBookParser + +create EditCommandParser +AddressBookParser -> EditCommandParser +activate EditCommandParser + +EditCommandParser --> AddressBookParser +deactivate EditCommandParser + +AddressBookParser -> EditCommandParser : parse("1 n/Bob") +activate EditCommandParser + +create EditCommand +EditCommandParser -> EditCommand +activate EditCommand + +EditCommand --> EditCommandParser : e +deactivate EditCommand + +EditCommandParser --> AddressBookParser : e +deactivate EditCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +EditCommandParser -[hidden]-> AddressBookParser +deactivate EditCommandParser + + +AddressBookParser --> LogicManager : e +deactivate AddressBookParser + +LogicManager -> EditCommand : execute() +activate EditCommand + +create Person +EditCommand -> Person : createEditedPerson(person, editPersonDescriptor) +activate Person + +Person --> EditCommand +deactivate Person + +EditCommand -> Model : setPerson(targetPerson, editedPerson) +activate Model + +Model --> EditCommand +deactivate Model + +create CommandResult +EditCommand -> CommandResult +activate CommandResult + +CommandResult --> EditCommand +deactivate CommandResult + +EditCommand --> LogicManager : result +deactivate EditCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/EditRecordSequenceDiagram.puml b/docs/diagrams/EditRecordSequenceDiagram.puml new file mode 100644 index 00000000000..14fdc7392f1 --- /dev/null +++ b/docs/diagrams/EditRecordSequenceDiagram.puml @@ -0,0 +1,92 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":EditRecordCommandParser" as EditRecordCommandParser LOGIC_COLOR +participant "e:EditRecordCommand" as EditRecordCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "model:Model" as Model MODEL_COLOR +participant "editedPerson:Person" as Person MODEL_COLOR +participant "editedRecord:Record" as Record MODEL_COLOR +end box + +[-> LogicManager : execute("editrecord 1/1 c/Fever") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("editrecord 1/1 c/Fever") +activate AddressBookParser + +create EditRecordCommandParser +AddressBookParser -> EditRecordCommandParser +activate EditRecordCommandParser + +EditRecordCommandParser --> AddressBookParser +deactivate EditRecordCommandParser + +AddressBookParser -> EditRecordCommandParser : parse("1/1 c/Fever") +activate EditRecordCommandParser + +create EditRecordCommand +EditRecordCommandParser -> EditRecordCommand +activate EditRecordCommand + +EditRecordCommand --> EditRecordCommandParser : e +deactivate EditRecordCommand + +EditRecordCommandParser --> AddressBookParser : e +deactivate EditRecordCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +EditRecordCommandParser -[hidden]-> AddressBookParser +deactivate EditRecordCommandParser + +AddressBookParser --> LogicManager : e +deactivate AddressBookParser + +LogicManager -> EditRecordCommand : execute(model) +activate EditRecordCommand + +create Record +EditRecordCommand -> Record : createEditedRecord(record, editRecordDescriptor) +activate Record + +Record --> EditRecordCommand +deactivate Record + +create Person +EditRecordCommand -> Person : createEditedPerson(person, editPersonDescriptor) +activate Person + +Person --> EditRecordCommand +deactivate Person + +EditRecordCommand -> Model : setPerson(targetPerson, editedPerson) +activate Model + +Model --> EditRecordCommand +deactivate Model + +EditRecordCommand -> Model : updateRecordList(newPerson) +activate Model + +Model --> EditRecordCommand +deactivate Model + +create CommandResult +EditRecordCommand -> CommandResult +activate CommandResult + +CommandResult --> EditRecordCommand +deactivate CommandResult + +EditRecordCommand --> LogicManager : result +deactivate EditRecordCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/FindRecordSequenceDiagram.puml b/docs/diagrams/FindRecordSequenceDiagram.puml new file mode 100644 index 00000000000..dbd6dae4bd5 --- /dev/null +++ b/docs/diagrams/FindRecordSequenceDiagram.puml @@ -0,0 +1,79 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":FindRecordCommandParser" as FindRecordCommandParser LOGIC_COLOR +participant "c:FindRecordCommand" as FindRecordCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant "predicate:RecordContainsKeywordsPredicate" as Predicate MODEL_COLOR +end box + + + +[-> LogicManager : "searchrecord Fever" +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("searchrecord Fever") +activate AddressBookParser + +create FindRecordCommandParser +AddressBookParser -> FindRecordCommandParser +activate FindRecordCommandParser + +FindRecordCommandParser --> AddressBookParser +deactivate FindRecordCommandParser + +AddressBookParser -> FindRecordCommandParser : parse("Fever") +activate FindRecordCommandParser + +create FindRecordCommand +FindRecordCommandParser -> FindRecordCommand +activate FindRecordCommand + +FindRecordCommand --> FindRecordCommandParser : c +deactivate FindRecordCommand + +FindRecordCommandParser --> AddressBookParser : c +deactivate FindRecordCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +FindRecordCommandParser -[hidden]-> AddressBookParser +deactivate FindRecordCommandParser + +AddressBookParser --> LogicManager : c +deactivate AddressBookParser + +LogicManager -> FindRecordCommand : execute(model) +activate FindRecordCommand + +FindRecordCommand -> Model : updateFilteredRecordList(predicate) +activate Model + +loop for each record in List + Model -> Predicate : test(record) + activate Predicate + Predicate --> Model : result + deactivate Predicate +end + +Model --> FindRecordCommand : filteredList +deactivate Model + +create CommandResult +FindRecordCommand -> CommandResult +activate CommandResult + +CommandResult --> FindRecordCommand +deactivate CommandResult + +FindRecordCommand --> LogicManager : result +deactivate FindRecordCommand + +@enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 0de5673070d..6c8fa4d3b9a 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -13,12 +13,11 @@ Class ModelManager Class UserPrefs Class UniquePersonList +Class UniqueRecordList +Class UniqueAppointmentList Class Person -Class Address -Class Email -Class Name -Class Phone -Class Tag +Class Record +Class Appointment Class I #FFFFFF } @@ -26,29 +25,29 @@ Class I #FFFFFF Class HiddenOutside #FFFFFF HiddenOutside ..> Model -AddressBook .up.|> ReadOnlyAddressBook +ReadOnlyUserPrefs -[hidden]right- Model +AddressBook .up.|> ReadOnlyAddressBook ModelManager .up.|> Model +UserPrefs .up.|> ReadOnlyUserPrefs Model .right.> ReadOnlyUserPrefs Model .left.> ReadOnlyAddressBook -ModelManager -left-> "1" AddressBook -ModelManager -right-> "1" UserPrefs -UserPrefs .up.|> ReadOnlyUserPrefs +ModelManager -down-> "1" AddressBook +ModelManager -down-> "1" UserPrefs -AddressBook *--> "1" UniquePersonList +AddressBook *--> "1" UniqueAppointmentList +AddressBook *--> "2" UniquePersonList +AddressBook *--> "1" UniqueRecordList +ModelManager --> "~* filtered" Person +ModelManager --> "~* filtered" Record UniquePersonList --> "~* all" Person -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -Person *--> "*" Tag +UniqueRecordList --> "~* all" Record +UniqueAppointmentList --> "~*" Appointment + +ModelManager -[hidden]down- UniquePersonList +ModelManager -[hidden]down- UniqueRecordList +UniqueAppointmentList -[hidden]right- UniquePersonList -Person -[hidden]up--> I -UniquePersonList -[hidden]right-> I -Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email -ModelManager --> "~* filtered" Person @enduml diff --git a/docs/diagrams/PersonClassDiagram.puml b/docs/diagrams/PersonClassDiagram.puml new file mode 100644 index 00000000000..55e37a00fdd --- /dev/null +++ b/docs/diagrams/PersonClassDiagram.puml @@ -0,0 +1,20 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +UniquePersonList -down-> "*" Person +Person *-down-> "1" Name +Person *-down-> "1" Nric +Person *-down-> "1" Gender +Person *-down-> "1" Age +Person *-down-> "1" Email +Person *-down-> "1" Phone +Person *-down-> "1" BloodType +Person *-down-> "*" Allergy +Person *-down-> "1" UniqueRecordList +Person *-down-> "1" UniqueAppointmentList + +@enduml diff --git a/docs/diagrams/PinSequenceDiagram.puml b/docs/diagrams/PinSequenceDiagram.puml new file mode 100644 index 00000000000..498a7fc29e2 --- /dev/null +++ b/docs/diagrams/PinSequenceDiagram.puml @@ -0,0 +1,71 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":PinCommandParser" as PinCommandParser LOGIC_COLOR +participant "c:PinCommand" as PinCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("pin 1") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("pin 1") +activate AddressBookParser + +create PinCommandParser +AddressBookParser -> PinCommandParser +activate PinCommandParser + +PinCommandParser --> AddressBookParser +deactivate PinCommandParser + +AddressBookParser -> PinCommandParser : parse("1") +activate PinCommandParser + +create PinCommand +PinCommandParser -> PinCommand +activate PinCommand + +PinCommand --> PinCommandParser : c +deactivate PinCommand + +PinCommandParser --> AddressBookParser : c +deactivate PinCommand +'Hidden arrow to position the destroy marker below the end of the activation bar. +PinCommandParser -[hidden]-> AddressBookParser +deactivate PinCommandParser + +AddressBookParser --> LogicManager : c +deactivate AddressBookParser + +LogicManager -> PinCommand : execute(model) +activate PinCommand + +PinCommand -> Model : setPerson(targetPerson, newPerson) +activate Model + +Model --> PinCommand +deactivate Model + +create CommandResult +PinCommand -> CommandResult +activate CommandResult + +CommandResult --> PinCommand +deactivate CommandResult + +PinCommand --> LogicManager : result +deactivate PinCommand + + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/RecordClassDiagram.puml b/docs/diagrams/RecordClassDiagram.puml new file mode 100644 index 00000000000..d7e00e48937 --- /dev/null +++ b/docs/diagrams/RecordClassDiagram.puml @@ -0,0 +1,12 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +UniqueRecordList -down-> "*" Record +Record *-down-> "1" DateTime +Record *-down-> "*" Condition +Record *-down-> "*" Medication +@enduml diff --git a/docs/diagrams/SearchSequenceDiagram.puml b/docs/diagrams/SearchSequenceDiagram.puml new file mode 100644 index 00000000000..e28a7d6c3c6 --- /dev/null +++ b/docs/diagrams/SearchSequenceDiagram.puml @@ -0,0 +1,79 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":FindCommandParser" as FindCommandParser LOGIC_COLOR +participant "c:FindCommand" as FindCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant "predicate:NameContainsKeywordsPredicate" as Predicate MODEL_COLOR +end box + + + +[-> LogicManager : "search Adam" +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("search Adam") +activate AddressBookParser + +create FindCommandParser +AddressBookParser -> FindCommandParser +activate FindCommandParser + +FindCommandParser --> AddressBookParser +deactivate FindCommandParser + +AddressBookParser -> FindCommandParser : parse("Adam") +activate FindCommandParser + +create FindCommand +FindCommandParser -> FindCommand +activate FindCommand + +FindCommand --> FindCommandParser : c +deactivate FindCommand + +FindCommandParser --> AddressBookParser : c +deactivate FindCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +FindCommandParser -[hidden]-> AddressBookParser +deactivate FindCommandParser + +AddressBookParser --> LogicManager : c +deactivate AddressBookParser + +LogicManager -> FindCommand : execute() +activate FindCommand + +FindCommand -> Model : updateFilteredPersonList(predicate) +activate Model + +loop for each person in List + Model -> Predicate : test(person) + activate Predicate + Predicate --> Model : result + deactivate Predicate +end + +Model --> FindCommand : return filteredList +deactivate Model + +create CommandResult +FindCommand -> CommandResult +activate CommandResult + +CommandResult --> FindCommand +deactivate CommandResult + +FindCommand --> LogicManager : result +deactivate FindCommand + +@enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index a821e06458c..3cd390932a9 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -19,7 +19,11 @@ Class "<>\nAddressBookStorage" as AddressBookStorage Class JsonAddressBookStorage Class JsonSerializableAddressBook Class JsonAdaptedPerson -Class JsonAdaptedTag +Class JsonAdaptedAllergy +Class JsonAdaptedRecord +Class JsonAdaptedAppointment +Class JsonAdaptedCondition +Class JsonAdaptedMedication } } @@ -38,6 +42,13 @@ JsonUserPrefsStorage .up.|> UserPrefsStorage JsonAddressBookStorage .up.|> AddressBookStorage JsonAddressBookStorage ..> JsonSerializableAddressBook JsonSerializableAddressBook --> "*" JsonAdaptedPerson -JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonAdaptedPerson --> "*" JsonAdaptedAllergy +JsonAdaptedPerson --> "*" JsonAdaptedRecord +JsonAdaptedPerson --> "*" JsonAdaptedAppointment +JsonAdaptedRecord --> "*" JsonAdaptedCondition +JsonAdaptedRecord --> "*" JsonAdaptedMedication + + + @enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..72d013da950 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -10,17 +10,15 @@ Class "{abstract}\nUiPart" as UiPart Class UiManager Class MainWindow Class HelpWindow +Class AppointmentsWindow Class ResultDisplay Class PersonListPanel -Class PersonCard +Class PinnedPersonListPanel +Class RecordListPanel Class StatusBarFooter Class CommandBox } -package Model <> { -Class HiddenModel #FFFFFF -} - package Logic <> { Class HiddenLogic #FFFFFF } @@ -32,29 +30,37 @@ UiManager .left.|> Ui UiManager -down-> "1" MainWindow MainWindow *-down-> "1" CommandBox MainWindow *-down-> "1" ResultDisplay -MainWindow *-down-> "1" PersonListPanel +MainWindow *-down-> "2" PersonListPanel +MainWindow *-down-> "1" RecordListPanel +MainWindow *-down-> "0..1" AppointmentsWindow +MainWindow *-down-> "1" PinnedPersonListPanel MainWindow *-down-> "1" StatusBarFooter MainWindow --> "0..1" HelpWindow -PersonListPanel -down-> "*" PersonCard + MainWindow -left-|> UiPart ResultDisplay --|> UiPart CommandBox --|> UiPart PersonListPanel --|> UiPart -PersonCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow --|> UiPart +PinnedPersonListPanel --|> UiPart +RecordListPanel --|> UiPart +AppointmentsWindow --|> UiPart -PersonCard ..> Model UiManager -right-> Logic MainWindow -left-> Logic + PersonListPanel -[hidden]left- HelpWindow HelpWindow -[hidden]left- CommandBox CommandBox -[hidden]left- ResultDisplay ResultDisplay -[hidden]left- StatusBarFooter +RecordListPanel -[hidden]left- PinnedPersonListPanel +AppointmentsWindow -[hidden]left- RecordListPanel + MainWindow -[hidden]-|> UiPart @enduml diff --git a/docs/diagrams/UiDetailedClassDiagram.puml b/docs/diagrams/UiDetailedClassDiagram.puml new file mode 100644 index 00000000000..f44d62423a7 --- /dev/null +++ b/docs/diagrams/UiDetailedClassDiagram.puml @@ -0,0 +1,54 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +package UI <>{ +Class "{abstract}\nUiPart" as UiPart +Class AppointmentsWindow +Class PersonListPanel +Class PinnedPersonListPanel +Class RecordListPanel +Class AppointmentListPanel +Class AppointmentCalendarPanel +Class PersonCard +Class RecordCard +Class AppointmentCard +} + +package Model <> { +Class HiddenModel #FFFFFF +} + +Class HiddenOutside #FFFFFF + +PersonListPanel -down-> "*" PersonCard + +PinnedPersonListPanel -down-> "*" PersonCard + +RecordListPanel -down-> "*" RecordCard + +AppointmentsWindow -down-> "1" AppointmentCalendarPanel + +AppointmentsWindow -down-> "1" AppointmentListPanel + +AppointmentListPanel -down-> "*" AppointmentCard +PersonListPanel --|> UiPart +PinnedPersonListPanel --|> UiPart +RecordListPanel --|> UiPart +AppointmentsWindow --|> UiPart +PersonCard --|> UiPart +RecordCard --|> UiPart +AppointmentListPanel --|> UiPart +AppointmentCard --|> UiPart +AppointmentCalendarPanel --|> UiPart + +AppointmentCalendarPanel ..> Model +PersonCard ..> Model +RecordCard ..> Model +AppointmentCard ..> Model + +AppointmentCalendarPanel -[hidden]left-> AppointmentListPanel + +@enduml diff --git a/docs/diagrams/UnpinSequenceDiagram.puml b/docs/diagrams/UnpinSequenceDiagram.puml new file mode 100644 index 00000000000..01d1112d732 --- /dev/null +++ b/docs/diagrams/UnpinSequenceDiagram.puml @@ -0,0 +1,71 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":UnpinCommandParser" as UnpinCommandParser LOGIC_COLOR +participant "c:UnpinCommand" as PinCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("unpin 1") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("unpin 1") +activate AddressBookParser + +create UnpinCommandParser +AddressBookParser -> UnpinCommandParser +activate UnpinCommandParser + +UnpinCommandParser --> AddressBookParser +deactivate UnpinCommandParser + +AddressBookParser -> UnpinCommandParser : parse("1") +activate UnpinCommandParser + +create PinCommand +UnpinCommandParser -> PinCommand +activate PinCommand + +PinCommand --> UnpinCommandParser : c +deactivate PinCommand + +UnpinCommandParser --> AddressBookParser : c +deactivate PinCommand +'Hidden arrow to position the destroy marker below the end of the activation bar. +UnpinCommandParser -[hidden]-> AddressBookParser +deactivate UnpinCommandParser + +AddressBookParser --> LogicManager : c +deactivate AddressBookParser + +LogicManager -> PinCommand : execute(model) +activate PinCommand + +PinCommand -> Model : setPerson(targetPerson, newPerson) +activate Model + +Model --> PinCommand +deactivate Model + +create CommandResult +PinCommand -> CommandResult +activate CommandResult + +CommandResult --> PinCommand +deactivate CommandResult + +PinCommand --> LogicManager : result +deactivate PinCommand + + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/ViewAppointmentSequenceDiagram.puml b/docs/diagrams/ViewAppointmentSequenceDiagram.puml new file mode 100644 index 00000000000..12264780cae --- /dev/null +++ b/docs/diagrams/ViewAppointmentSequenceDiagram.puml @@ -0,0 +1,71 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box UI UI_COLOR_T1 +participant ":MainWindow" as MainWindow UI_COLOR +participant ":AppointmentsWindow" as AppointmentsWindow UI_COLOR +end box + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant "v:ViewAppointmentCommand" as ViewAppointmentCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +[-> MainWindow : "viewappointment" +activate MainWindow + +MainWindow -> LogicManager : execute("viewappointment") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("viewappointment") +activate AddressBookParser + +create ViewAppointmentCommand +AddressBookParser -> ViewAppointmentCommand +activate ViewAppointmentCommand + +ViewAppointmentCommand --> AddressBookParser : v +deactivate ViewAppointmentCommand + +AddressBookParser --> LogicManager : v +deactivate AddressBookParser + +LogicManager -> ViewAppointmentCommand : execute(model) +activate ViewAppointmentCommand + +create CommandResult +ViewAppointmentCommand -> CommandResult +activate CommandResult + +CommandResult --> ViewAppointmentCommand +deactivate CommandResult + +ViewAppointmentCommand --> LogicManager : result +deactivate ViewAppointmentCommand +ViewAppointmentCommand -[hidden]-> LogicManager : result + +MainWindow <-- LogicManager : result +deactivate LogicManager + +MainWindow -> MainWindow : handleAppointment() +activate MainWindow + +MainWindow -> AppointmentsWindow : show() +activate AppointmentsWindow + +AppointmentsWindow -> AppointmentsWindow : fillInnerParts() +activate AppointmentsWindow +deactivate AppointmentsWindow + +AppointmentsWindow --> MainWindow +deactivate AppointmentsWindow + +deactivate MainWindow + +[<-- MainWindow +deactivate MainWindow + +@enduml diff --git a/docs/diagrams/ViewSequenceDiagram.puml b/docs/diagrams/ViewSequenceDiagram.puml new file mode 100644 index 00000000000..7a0f0e55c06 --- /dev/null +++ b/docs/diagrams/ViewSequenceDiagram.puml @@ -0,0 +1,71 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":ViewCommandParser" as ViewCommandParser LOGIC_COLOR +participant "v:ViewCommand" as ViewCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("view 1") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("view 1") +activate AddressBookParser + +create ViewCommandParser +AddressBookParser -> ViewCommandParser +activate ViewCommandParser + +ViewCommandParser --> AddressBookParser +deactivate ViewCommandParser + +AddressBookParser -> ViewCommandParser : parse("1") +activate ViewCommandParser + +Create ViewCommand +ViewCommandParser -> ViewCommand +activate ViewCommand + +ViewCommand --> ViewCommandParser : v +deactivate ViewCommand + +ViewCommandParser --> AddressBookParser : v +deactivate ViewCommandParser + +ViewCommandParser -[hidden]-> AddressBookParser +deactivate ViewCommandParser + +AddressBookParser --> LogicManager : v +deactivate AddressBookParser + +LogicManager -> ViewCommand : execute(model) +activate ViewCommand + +ViewCommand -> Model : updateRecordList(Person) +activate Model + +Model --> ViewCommand +deactivate Model + +create CommandResult +ViewCommand -> CommandResult +activate CommandResult + +CommandResult --> ViewCommand +deactivate CommandResult + +ViewCommand --> LogicManager : result +deactivate ViewCommand + +[<-- LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/images/AddPatientSuccess.png b/docs/images/AddPatientSuccess.png new file mode 100644 index 00000000000..aa8e3ce1a85 Binary files /dev/null and b/docs/images/AddPatientSuccess.png differ diff --git a/docs/images/AddRecordSuccess.png b/docs/images/AddRecordSuccess.png new file mode 100644 index 00000000000..56f57fba205 Binary files /dev/null and b/docs/images/AddRecordSuccess.png differ diff --git a/docs/images/ArchitectureDiagram.png b/docs/images/ArchitectureDiagram.png deleted file mode 100644 index cd540665053..00000000000 Binary files a/docs/images/ArchitectureDiagram.png and /dev/null differ diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png deleted file mode 100644 index 37ad06a2803..00000000000 Binary files a/docs/images/ArchitectureSequenceDiagram.png and /dev/null differ diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png deleted file mode 100644 index 02a42e35e76..00000000000 Binary files a/docs/images/BetterModelClassDiagram.png and /dev/null differ diff --git a/docs/images/CommitActivityDiagram.png b/docs/images/CommitActivityDiagram.png deleted file mode 100644 index 5b464126b35..00000000000 Binary files a/docs/images/CommitActivityDiagram.png and /dev/null differ diff --git a/docs/images/ComponentManagers.png b/docs/images/ComponentManagers.png deleted file mode 100644 index ae52a35718a..00000000000 Binary files a/docs/images/ComponentManagers.png and /dev/null differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png deleted file mode 100644 index e186f7ba096..00000000000 Binary files a/docs/images/DeleteSequenceDiagram.png and /dev/null differ diff --git a/docs/images/HelpWindow.png b/docs/images/HelpWindow.png new file mode 100644 index 00000000000..77238ca13fd Binary files /dev/null and b/docs/images/HelpWindow.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png deleted file mode 100644 index e3b784310fe..00000000000 Binary files a/docs/images/LogicClassDiagram.png and /dev/null differ diff --git a/docs/images/LogicStorageDIP.png b/docs/images/LogicStorageDIP.png deleted file mode 100644 index 871157f5a9c..00000000000 Binary files a/docs/images/LogicStorageDIP.png and /dev/null differ diff --git a/docs/images/MacUserDir.png b/docs/images/MacUserDir.png new file mode 100644 index 00000000000..779b9c78ef1 Binary files /dev/null and b/docs/images/MacUserDir.png differ diff --git a/docs/images/MedBookUi.png b/docs/images/MedBookUi.png new file mode 100644 index 00000000000..8c43bb27353 Binary files /dev/null and b/docs/images/MedBookUi.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png deleted file mode 100644 index a19fb1b4ac8..00000000000 Binary files a/docs/images/ModelClassDiagram.png and /dev/null differ diff --git a/docs/images/ParserClasses.png b/docs/images/ParserClasses.png deleted file mode 100644 index edfd1ff7897..00000000000 Binary files a/docs/images/ParserClasses.png and /dev/null differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png deleted file mode 100644 index 18fa4d0d51f..00000000000 Binary files a/docs/images/StorageClassDiagram.png and /dev/null differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..0f3a7b49fe1 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png deleted file mode 100644 index 11f06d68671..00000000000 Binary files a/docs/images/UiClassDiagram.png and /dev/null differ diff --git a/docs/images/UiComponent.png b/docs/images/UiComponent.png new file mode 100644 index 00000000000..3e9d5e5c0af Binary files /dev/null and b/docs/images/UiComponent.png differ diff --git a/docs/images/UndoRedoState0.png b/docs/images/UndoRedoState0.png deleted file mode 100644 index c5f91b58533..00000000000 Binary files a/docs/images/UndoRedoState0.png and /dev/null differ diff --git a/docs/images/UndoRedoState1.png b/docs/images/UndoRedoState1.png deleted file mode 100644 index 2d3ad09c047..00000000000 Binary files a/docs/images/UndoRedoState1.png and /dev/null differ diff --git a/docs/images/UndoRedoState2.png b/docs/images/UndoRedoState2.png deleted file mode 100644 index 20853694e03..00000000000 Binary files a/docs/images/UndoRedoState2.png and /dev/null differ diff --git a/docs/images/UndoRedoState3.png b/docs/images/UndoRedoState3.png deleted file mode 100644 index 1a9551b31be..00000000000 Binary files a/docs/images/UndoRedoState3.png and /dev/null differ diff --git a/docs/images/UndoRedoState4.png b/docs/images/UndoRedoState4.png deleted file mode 100644 index 46dfae78c94..00000000000 Binary files a/docs/images/UndoRedoState4.png and /dev/null differ diff --git a/docs/images/UndoRedoState5.png b/docs/images/UndoRedoState5.png deleted file mode 100644 index f45889b5fdf..00000000000 Binary files a/docs/images/UndoRedoState5.png and /dev/null differ diff --git a/docs/images/UndoSequenceDiagram.png b/docs/images/UndoSequenceDiagram.png deleted file mode 100644 index c7a7e637266..00000000000 Binary files a/docs/images/UndoSequenceDiagram.png and /dev/null differ diff --git a/docs/images/View.png b/docs/images/View.png new file mode 100644 index 00000000000..43f170929cd Binary files /dev/null and b/docs/images/View.png differ diff --git a/docs/images/ViewAppointmentWindow.png b/docs/images/ViewAppointmentWindow.png new file mode 100644 index 00000000000..3dba935bfe8 Binary files /dev/null and b/docs/images/ViewAppointmentWindow.png differ diff --git a/docs/images/WinUserDir.png b/docs/images/WinUserDir.png new file mode 100644 index 00000000000..4aaea8304a9 Binary files /dev/null and b/docs/images/WinUserDir.png differ diff --git a/docs/images/adammangzijun.png b/docs/images/adammangzijun.png new file mode 100644 index 00000000000..5614c0a78ba Binary files /dev/null and b/docs/images/adammangzijun.png differ diff --git a/docs/images/clin-lyx.png b/docs/images/clin-lyx.png new file mode 100644 index 00000000000..008876cf993 Binary files /dev/null and b/docs/images/clin-lyx.png differ diff --git a/docs/images/darren159.png b/docs/images/darren159.png new file mode 100644 index 00000000000..1e0ffecbcd8 Binary files /dev/null and b/docs/images/darren159.png differ diff --git a/docs/images/hjoneweek.png b/docs/images/hjoneweek.png new file mode 100644 index 00000000000..d21bba2e560 Binary files /dev/null and b/docs/images/hjoneweek.png differ diff --git a/docs/images/ryanongwx.png b/docs/images/ryanongwx.png new file mode 100644 index 00000000000..f8fb851ef45 Binary files /dev/null and b/docs/images/ryanongwx.png differ diff --git a/docs/images/screenshots/addAppointment - after.png b/docs/images/screenshots/addAppointment - after.png new file mode 100644 index 00000000000..ec8bdc41eb5 Binary files /dev/null and b/docs/images/screenshots/addAppointment - after.png differ diff --git a/docs/images/screenshots/addAppointment.png b/docs/images/screenshots/addAppointment.png new file mode 100644 index 00000000000..ca6cfba83c1 Binary files /dev/null and b/docs/images/screenshots/addAppointment.png differ diff --git a/docs/images/screenshots/addPatient - after.png b/docs/images/screenshots/addPatient - after.png new file mode 100644 index 00000000000..71e7953ff9e Binary files /dev/null and b/docs/images/screenshots/addPatient - after.png differ diff --git a/docs/images/screenshots/addPatient.png b/docs/images/screenshots/addPatient.png new file mode 100644 index 00000000000..489d575e0ce Binary files /dev/null and b/docs/images/screenshots/addPatient.png differ diff --git a/docs/images/screenshots/addRecord - after.png b/docs/images/screenshots/addRecord - after.png new file mode 100644 index 00000000000..0670697fc36 Binary files /dev/null and b/docs/images/screenshots/addRecord - after.png differ diff --git a/docs/images/screenshots/addRecord.png b/docs/images/screenshots/addRecord.png new file mode 100644 index 00000000000..1538b6772a8 Binary files /dev/null and b/docs/images/screenshots/addRecord.png differ diff --git a/docs/images/screenshots/attachFile - buttonPress.png b/docs/images/screenshots/attachFile - buttonPress.png new file mode 100644 index 00000000000..85bb31a405e Binary files /dev/null and b/docs/images/screenshots/attachFile - buttonPress.png differ diff --git a/docs/images/screenshots/attachFile - openFile.png b/docs/images/screenshots/attachFile - openFile.png new file mode 100644 index 00000000000..e8ad06bbcf3 Binary files /dev/null and b/docs/images/screenshots/attachFile - openFile.png differ diff --git a/docs/images/screenshots/attachFile - selectFile.png b/docs/images/screenshots/attachFile - selectFile.png new file mode 100644 index 00000000000..2fd3a8d073f Binary files /dev/null and b/docs/images/screenshots/attachFile - selectFile.png differ diff --git a/docs/images/screenshots/delete - after.jpg b/docs/images/screenshots/delete - after.jpg new file mode 100644 index 00000000000..e7e72fa5016 Binary files /dev/null and b/docs/images/screenshots/delete - after.jpg differ diff --git a/docs/images/screenshots/delete.jpg b/docs/images/screenshots/delete.jpg new file mode 100644 index 00000000000..0b1ba930099 Binary files /dev/null and b/docs/images/screenshots/delete.jpg differ diff --git a/docs/images/screenshots/editPatient - after.png b/docs/images/screenshots/editPatient - after.png new file mode 100644 index 00000000000..0ec7c1017b4 Binary files /dev/null and b/docs/images/screenshots/editPatient - after.png differ diff --git a/docs/images/screenshots/editPatient.png b/docs/images/screenshots/editPatient.png new file mode 100644 index 00000000000..89d7e5b0f42 Binary files /dev/null and b/docs/images/screenshots/editPatient.png differ diff --git a/docs/images/screenshots/pin - after.png b/docs/images/screenshots/pin - after.png new file mode 100644 index 00000000000..e27300f5207 Binary files /dev/null and b/docs/images/screenshots/pin - after.png differ diff --git a/docs/images/screenshots/pin.png b/docs/images/screenshots/pin.png new file mode 100644 index 00000000000..18033f31b6b Binary files /dev/null and b/docs/images/screenshots/pin.png differ diff --git a/docs/images/screenshots/search - after.png b/docs/images/screenshots/search - after.png new file mode 100644 index 00000000000..4bc007865bc Binary files /dev/null and b/docs/images/screenshots/search - after.png differ diff --git a/docs/images/screenshots/search.png b/docs/images/screenshots/search.png new file mode 100644 index 00000000000..cd7d5f67d9d Binary files /dev/null and b/docs/images/screenshots/search.png differ diff --git a/docs/images/screenshots/view - after.png b/docs/images/screenshots/view - after.png new file mode 100644 index 00000000000..baba00ad153 Binary files /dev/null and b/docs/images/screenshots/view - after.png differ diff --git a/docs/images/screenshots/view.png b/docs/images/screenshots/view.png new file mode 100644 index 00000000000..de85e219b59 Binary files /dev/null and b/docs/images/screenshots/view.png differ diff --git a/docs/images/screenshots/viewAppointment.png b/docs/images/screenshots/viewAppointment.png new file mode 100644 index 00000000000..160c0d39dd0 Binary files /dev/null and b/docs/images/screenshots/viewAppointment.png differ diff --git a/docs/images/tracing/LogicSequenceDiagram.png b/docs/images/tracing/LogicSequenceDiagram.png deleted file mode 100644 index 25c8b66b9f1..00000000000 Binary files a/docs/images/tracing/LogicSequenceDiagram.png and /dev/null differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..b17bdc57a3b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,19 +1,20 @@ --- -layout: page -title: AddressBook Level-3 + layout: default.md + title: "" --- -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) -[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3) +# MedBook -![Ui](images/Ui.png) +[![CI Status](https://github.com/AY2324S1-CS2103T-T12-4/tp/actions/workflows/gradle.yml/badge.svg)](https://github.com/AY2324S1-CS2103T-T12-4/tp/actions) +[![codecov](https://codecov.io/gh/AY2324S1-CS2103T-T12-4/tp/graph/badge.svg?token=XRD2EIUJ8H)](https://codecov.io/gh/AY2324S1-CS2103T-T12-4/tp) -**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). +![Ui](images/Ui.png) -* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). -* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. +**MedBook is a desktop application for managing your patient medical records.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). +- If you are interested in using MedBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +- If you are interested about developing MedBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. **Acknowledgements** -* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5) +- Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5) diff --git a/docs/package-lock.json b/docs/package-lock.json new file mode 100644 index 00000000000..63a232e05dc --- /dev/null +++ b/docs/package-lock.json @@ -0,0 +1,8587 @@ +{ + "name": "docs", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "docs", + "version": "1.0.0", + "devDependencies": { + "markbind-cli": "^5.1.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@fortawesome/fontawesome-free": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.2.tgz", + "integrity": "sha512-m5cPn3e2+FDCOgi1mz0RexTUvvQibBebOUlUlW0+YrMjDTPkiJ6VTKukA1GRsvRw+12KyJndNjj0O4AgTxm2Pg==", + "dev": true, + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/@kwsites/file-exists/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@kwsites/file-exists/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "dev": true + }, + "node_modules/@markbind/core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@markbind/core/-/core-5.1.0.tgz", + "integrity": "sha512-YAXjH+qCXnrBzpKIAJkayVLmyIUaG/8Dms3Gpd2VIufeZyW8w0diXdgKSsymjzodTMgghZMdxG3Qpng833ARPg==", + "dev": true, + "dependencies": { + "@fortawesome/fontawesome-free": "^6.4.0", + "@markbind/core-web": "5.1.0", + "@primer/octicons": "^15.0.1", + "@sindresorhus/slugify": "^0.9.1", + "@tlylt/markdown-it-imsize": "^3.0.0", + "bluebird": "^3.7.2", + "bootswatch": "5.1.3", + "cheerio": "^0.22.0", + "crypto-js": "^4.0.0", + "csv-parse": "^4.14.2", + "ensure-posix-path": "^1.1.1", + "fastmatter": "^2.1.1", + "fs-extra": "^9.0.1", + "gh-pages": "^2.1.1", + "highlight.js": "^10.4.1", + "htmlparser2": "^3.10.1", + "ignore": "^5.1.4", + "js-beautify": "1.14.3", + "katex": "^0.15.6", + "lodash": "^4.17.15", + "markdown-it": "^12.3.2", + "markdown-it-attrs": "^4.1.3", + "markdown-it-emoji": "^1.4.0", + "markdown-it-linkify-images": "^3.0.0", + "markdown-it-mark": "^3.0.0", + "markdown-it-regexp": "^0.4.0", + "markdown-it-sub": "^1.0.0", + "markdown-it-sup": "^1.0.0", + "markdown-it-table-of-contents": "^0.4.4", + "markdown-it-task-lists": "^2.1.1", + "markdown-it-texmath": "^1.0.0", + "markdown-it-video": "^0.6.3", + "material-icons": "^1.9.1", + "moment": "^2.29.4", + "nunjucks": "3.2.2", + "path-is-inside": "^1.0.2", + "simple-git": "^2.17.0", + "url-parse": "^1.5.10", + "uuid": "^8.3.1", + "vue": "2.6.14", + "vue-server-renderer": "2.6.14", + "vue-template-compiler": "2.6.14", + "walk-sync": "^2.0.2", + "winston": "^2.4.4" + } + }, + "node_modules/@markbind/core-web": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@markbind/core-web/-/core-web-5.1.0.tgz", + "integrity": "sha512-TRzz8ZCr25pylKvFxF/WwXDi4Gbtsb2OLXV61WyTFqVy03tFoEJ2mqncpbliI9DrfDdKWcm1YZPgDCedVkYjKA==", + "dev": true + }, + "node_modules/@primer/octicons": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-15.2.0.tgz", + "integrity": "sha512-4cHZzcZ3F/HQNL4EKSaFyVsW7XtITiJkTeB1JDDmRuP/XobyWyF9gWxuV9c+byUa8dOB5KNQn37iRvNrIehPUQ==", + "dev": true, + "dependencies": { + "object-assign": "^4.1.1" + } + }, + "node_modules/@sindresorhus/slugify": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-0.9.1.tgz", + "integrity": "sha512-b6heYM9dzZD13t2GOiEQTDE0qX+I1GyOotMwKh9VQqzuNiVdPVT8dM43fe9HNb/3ul+Qwd5oKSEDrDIfhq3bnQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5", + "lodash.deburr": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@tlylt/markdown-it-imsize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@tlylt/markdown-it-imsize/-/markdown-it-imsize-3.0.0.tgz", + "integrity": "sha512-6kTM+vRJTuN2UxNPyJ8yC+NHrzS+MxVHV+z+bDxSr/Fd7eTah2+otLKC2B17YI/1lQnSumA2qokPGuzsA98c6g==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, + "node_modules/a-sync-waterfall": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", + "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/apache-crypt": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/apache-crypt/-/apache-crypt-1.2.5.tgz", + "integrity": "sha512-ICnYQH+DFVmw+S4Q0QY2XRXD8Ne8ewh8HgbuFH4K7022zCxgHM0Hz1xkRnUlEfAXNbwp1Cnhbedu60USIfDxvg==", + "dev": true, + "dependencies": { + "unix-crypt-td-js": "^1.1.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/apache-md5": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/apache-md5/-/apache-md5-1.1.7.tgz", + "integrity": "sha512-JtHjzZmJxtzfTSjsCyHgPR155HBe5WGyUyHTaEkfy46qhwCFKx1Epm6nAxgUG3WfUZP1dWhGqj9Z2NOBeZ+uBw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "dependencies": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/bootswatch": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bootswatch/-/bootswatch-5.1.3.tgz", + "integrity": "sha512-NmZFN6rOCoXWQ/PkzmD8FFWDe24kocX9OXWHNVaLxVVnpqpAzEbMFsf8bAfKwVtpNXibasZCzv09B5fLieAh2g==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cheerio": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", + "integrity": "sha512-8/MzidM6G/TgRelkzDG13y3Y9LxBjCb+8yOEZ9+wwq5gVF2w2pV0wmHvjfT0RvuxGyR7UEuK36r+yYMbT4uKgA==", + "dev": true, + "dependencies": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash.assignin": "^4.0.9", + "lodash.bind": "^4.1.4", + "lodash.defaults": "^4.0.1", + "lodash.filter": "^4.4.0", + "lodash.flatten": "^4.2.0", + "lodash.foreach": "^4.3.0", + "lodash.map": "^4.4.0", + "lodash.merge": "^4.4.0", + "lodash.pick": "^4.2.1", + "lodash.reduce": "^4.4.0", + "lodash.reject": "^4.4.0", + "lodash.some": "^4.4.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", + "dev": true, + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/crypto-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==", + "dev": true + }, + "node_modules/css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha512-dUQOBoqdR7QwV90WysXPLXG5LO7nhYBgiWVfxF80DKPF8zx1t/pUd2FYy73emg3zrjtM6dzmYgbHKfV2rxiHQA==", + "dev": true, + "dependencies": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "node_modules/css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/csv-parse": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.16.3.tgz", + "integrity": "sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==", + "dev": true + }, + "node_modules/cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "dev": true, + "dependencies": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "node_modules/domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==", + "dev": true, + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "node_modules/editorconfig": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", + "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", + "dev": true, + "dependencies": { + "commander": "^2.19.0", + "lru-cache": "^4.1.5", + "semver": "^5.6.0", + "sigmund": "^1.0.1" + }, + "bin": { + "editorconfig": "bin/editorconfig" + } + }, + "node_modules/editorconfig/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/email-addresses": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", + "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ensure-posix-path": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ensure-posix-path/-/ensure-posix-path-1.1.1.tgz", + "integrity": "sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw==", + "dev": true + }, + "node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "node_modules/event-stream/node_modules/split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", + "dev": true, + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/event-stream/node_modules/stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1" + } + }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "dev": true, + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "dev": true, + "engines": { + "node": "> 0.1.90" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, + "node_modules/fastmatter": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fastmatter/-/fastmatter-2.1.1.tgz", + "integrity": "sha512-NFrjZEPJZTexoJEuyM5J7n4uFaLf0dOI7Ok4b2IZXOYBqCp1Bh5RskANmQ2TuDsz3M35B1yL2AP/Rn+kp85KeA==", + "dev": true, + "dependencies": { + "js-yaml": "^3.13.0", + "split": "^1.0.1", + "stream-combiner": "^0.2.2", + "through2": "^3.0.1" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fecha": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", + "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==", + "dev": true + }, + "node_modules/figlet": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.5.2.tgz", + "integrity": "sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/file-stream-rotator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.4.1.tgz", + "integrity": "sha512-W3aa3QJEc8BS2MmdVpQiYLKHj3ijpto1gMDlsgCRSKfIUe6MwkcpODGPQ3vZfb0XvCeCqlu9CBQTN7oQri2TZQ==", + "dev": true, + "dependencies": { + "moment": "^2.11.2" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, + "node_modules/filename-reserved-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz", + "integrity": "sha512-UZArj7+U+2reBBVCvVmRlyq9D7EYQdUtuNN+1iz7pF1jGcJ2L0TjiRCxsTZfj2xFbM4c25uGCUDpKTHA7L2TKg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/filenamify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.1.tgz", + "integrity": "sha512-DKVP0WQcB7WaIMSwDETqImRej2fepPqvXQjaVib7LRZn9Rxn5UbvK2tYTqGf1A1DkIprQQkG4XSQXSOZp7Q3GQ==", + "dev": true, + "dependencies": { + "filename-reserved-regex": "^1.0.0", + "strip-outer": "^1.0.0", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/filenamify-url": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/filenamify-url/-/filenamify-url-1.0.0.tgz", + "integrity": "sha512-O9K9JcZeF5VdZWM1qR92NSv1WY2EofwudQayPx5dbnnFl9k0IcZha4eV/FGkjnBK+1irOQInij0yiooCHu/0Fg==", + "dev": true, + "dependencies": { + "filenamify": "^1.0.0", + "humanize-url": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", + "dev": true, + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", + "dev": true + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gh-pages": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-2.2.0.tgz", + "integrity": "sha512-c+yPkNOPMFGNisYg9r4qvsMIjVYikJv7ImFOhPIVPt0+AcRUamZ7zkGRLHz7FKB0xrlZ+ddSOJsZv9XAFVXLmA==", + "dev": true, + "dependencies": { + "async": "^2.6.1", + "commander": "^2.18.0", + "email-addresses": "^3.0.1", + "filenamify-url": "^1.0.0", + "fs-extra": "^8.1.0", + "globby": "^6.1.0" + }, + "bin": { + "gh-pages": "bin/gh-pages.js", + "gh-pages-clean": "bin/gh-pages-clean.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/gh-pages/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/gh-pages/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/gh-pages/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/gh-pages/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", + "dev": true, + "dependencies": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", + "dev": true, + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", + "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", + "dev": true + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "dependencies": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "node_modules/http-auth": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/http-auth/-/http-auth-3.1.3.tgz", + "integrity": "sha512-Jbx0+ejo2IOx+cRUYAGS1z6RGc6JfYUNkysZM4u4Sfk1uLlGv814F7/PIjQQAuThLdAWxb74JMGd5J8zex1VQg==", + "dev": true, + "dependencies": { + "apache-crypt": "^1.1.2", + "apache-md5": "^1.0.6", + "bcryptjs": "^2.3.0", + "uuid": "^3.0.0" + }, + "engines": { + "node": ">=4.6.1" + } + }, + "node_modules/http-auth/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "node_modules/humanize-url": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/humanize-url/-/humanize-url-1.0.1.tgz", + "integrity": "sha512-RtgTzXCPVb/te+e82NDhAc5paj+DuKSratIGAr+v+HZK24eAQ8LMoBGYoL7N/O+9iEc33AKHg45dOMKw3DNldQ==", + "dev": true, + "dependencies": { + "normalize-url": "^1.0.0", + "strip-url-auth": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true + }, + "node_modules/js-beautify": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.3.tgz", + "integrity": "sha512-f1ra8PHtOEu/70EBnmiUlV8nJePS58y9qKjl4JHfYWlFH6bo7ogZBz//FAZp7jDuXtYnGYKymZPlrg2I/9Zo4g==", + "dev": true, + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^0.15.3", + "glob": "^7.1.3", + "nopt": "^5.0.0" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/katex": { + "version": "0.15.6", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.15.6.tgz", + "integrity": "sha512-UpzJy4yrnqnhXvRPhjEuLA4lcPn6eRngixW7Q3TJErjg3Aw2PuLFBzTkdUb89UtumxjhHTqL3a5GDGETMSwgJA==", + "dev": true, + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.0.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dev": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/live-server": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/live-server/-/live-server-1.2.1.tgz", + "integrity": "sha512-Yn2XCVjErTkqnM3FfTmM7/kWy3zP7+cEtC7x6u+wUzlQ+1UW3zEYbbyJrc0jNDwiMDZI0m4a0i3dxlGHVyXczw==", + "dev": true, + "dependencies": { + "chokidar": "^2.0.4", + "colors": "latest", + "connect": "^3.6.6", + "cors": "latest", + "event-stream": "3.3.4", + "faye-websocket": "0.11.x", + "http-auth": "3.1.x", + "morgan": "^1.9.1", + "object-assign": "latest", + "opn": "latest", + "proxy-middleware": "latest", + "send": "latest", + "serve-index": "^1.9.1" + }, + "bin": { + "live-server": "live-server.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "dependencies": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "node_modules/live-server/node_modules/anymatch/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", + "dev": true, + "dependencies": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "optionalDependencies": { + "fsevents": "^1.2.7" + } + }, + "node_modules/live-server/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/live-server/node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", + "dev": true, + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/live-server/node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", + "dev": true, + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/live-server/node_modules/readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/live-server/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/live-server/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==", + "dev": true + }, + "node_modules/lodash.assignin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", + "integrity": "sha512-yX/rx6d/UTVh7sSVWVSIMjfnz95evAgDFdb1ZozC35I9mSFCkmzptOzevxjgbQUsc78NR44LVHWjsoMQXy9FDg==", + "dev": true + }, + "node_modules/lodash.bind": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", + "integrity": "sha512-lxdsn7xxlCymgLYo1gGvVrfHmkjDiyqVv62FAeF2i5ta72BipE1SLxw8hPEPLhD4/247Ijw07UQH7Hq/chT5LA==", + "dev": true + }, + "node_modules/lodash.deburr": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/lodash.deburr/-/lodash.deburr-4.1.0.tgz", + "integrity": "sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==", + "dev": true + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "dev": true + }, + "node_modules/lodash.filter": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", + "integrity": "sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ==", + "dev": true + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true + }, + "node_modules/lodash.foreach": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==", + "dev": true + }, + "node_modules/lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==", + "dev": true + }, + "node_modules/lodash.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==", + "dev": true + }, + "node_modules/lodash.reject": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", + "integrity": "sha512-qkTuvgEzYdyhiJBx42YPzPo71R1aEr0z79kAv7Ixg8wPFEjgRgJdUsGMG3Hf3OYSF/kHI79XhNlt+5Ar6OzwxQ==", + "dev": true + }, + "node_modules/lodash.some": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", + "integrity": "sha512-j7MJE+TuT51q9ggt4fSgVqro163BEFjAt3u97IqU+JA2DkWl80nFTrowzLpZ/BnpN7rrl0JA/593NAdd8p/scQ==", + "dev": true + }, + "node_modules/lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "dev": true, + "dependencies": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "node_modules/lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "dev": true, + "dependencies": { + "lodash._reinterpolate": "^3.0.0" + } + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true + }, + "node_modules/logform": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-1.10.0.tgz", + "integrity": "sha512-em5ojIhU18fIMOw/333mD+ZLE2fis0EzXl1ZwHx4iQzmpQi6odNiY/t+ITNr33JZhT9/KEaH+UPIipr6a9EjWg==", + "dev": true, + "dependencies": { + "colors": "^1.2.1", + "fast-safe-stringify": "^2.0.4", + "fecha": "^2.3.3", + "ms": "^2.1.1", + "triple-beam": "^1.2.0" + } + }, + "node_modules/logform/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", + "dev": true + }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", + "dev": true, + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/markbind-cli": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/markbind-cli/-/markbind-cli-5.1.0.tgz", + "integrity": "sha512-6POI1Q++2aZa+Udk/oQ6LX1oNPbKUBDY0mN3Up7VOFeK+XYW51faxuCk2Q91JTBxYRKLNtshxf0y12kB4Cj9Qw==", + "dev": true, + "dependencies": { + "@markbind/core": "5.1.0", + "@markbind/core-web": "5.1.0", + "bluebird": "^3.7.2", + "chalk": "^3.0.0", + "cheerio": "^0.22.0", + "chokidar": "^3.3.0", + "colors": "1.4.0", + "commander": "^8.1.0", + "figlet": "^1.2.4", + "find-up": "^4.1.0", + "fs-extra": "^9.0.1", + "live-server": "1.2.1", + "lodash": "^4.17.15", + "url-parse": "^1.5.10", + "winston": "^2.4.4", + "winston-daily-rotate-file": "^3.10.0" + }, + "bin": { + "markbind": "index.js" + } + }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-attrs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/markdown-it-attrs/-/markdown-it-attrs-4.1.6.tgz", + "integrity": "sha512-O7PDKZlN8RFMyDX13JnctQompwrrILuz2y43pW2GagcwpIIElkAdfeek+erHfxUOlXWPsjFeWmZ8ch1xtRLWpA==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "markdown-it": ">= 9.0.0" + } + }, + "node_modules/markdown-it-emoji": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-1.4.0.tgz", + "integrity": "sha512-QCz3Hkd+r5gDYtS2xsFXmBYrgw6KuWcJZLCEkdfAuwzZbShCmCfta+hwAMq4NX/4xPzkSHduMKgMkkPUJxSXNg==", + "dev": true + }, + "node_modules/markdown-it-linkify-images": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-linkify-images/-/markdown-it-linkify-images-3.0.0.tgz", + "integrity": "sha512-Vs5yGJa5MWjFgytzgtn8c1U6RcStj3FZKhhx459U8dYbEE5FTWZ6mMRkYMiDlkFO0j4VCsQT1LT557bY0ETgtg==", + "dev": true, + "dependencies": { + "markdown-it": "^13.0.1" + } + }, + "node_modules/markdown-it-linkify-images/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/markdown-it-linkify-images/node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/markdown-it-linkify-images/node_modules/linkify-it": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "dev": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/markdown-it-linkify-images/node_modules/markdown-it": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz", + "integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-mark": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/markdown-it-mark/-/markdown-it-mark-3.0.1.tgz", + "integrity": "sha512-HyxjAu6BRsdt6Xcv6TKVQnkz/E70TdGXEFHRYBGLncRE9lBFwDNLVtFojKxjJWgJ+5XxUwLaHXy+2sGBbDn+4A==", + "dev": true + }, + "node_modules/markdown-it-regexp": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/markdown-it-regexp/-/markdown-it-regexp-0.4.0.tgz", + "integrity": "sha512-0XQmr46K/rMKnI93Y3CLXsHj4jIioRETTAiVnJnjrZCEkGaDOmUxTbZj/aZ17G5NlRcVpWBYjqpwSlQ9lj+Kxw==", + "dev": true + }, + "node_modules/markdown-it-sub": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-sub/-/markdown-it-sub-1.0.0.tgz", + "integrity": "sha512-z2Rm/LzEE1wzwTSDrI+FlPEveAAbgdAdPhdWarq/ZGJrGW/uCQbKAnhoCsE4hAbc3SEym26+W2z/VQB0cQiA9Q==", + "dev": true + }, + "node_modules/markdown-it-sup": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-sup/-/markdown-it-sup-1.0.0.tgz", + "integrity": "sha512-E32m0nV9iyhRR7CrhnzL5msqic7rL1juWre6TQNxsnApg7Uf+F97JOKxUijg5YwXz86lZ0mqfOnutoryyNdntQ==", + "dev": true + }, + "node_modules/markdown-it-table-of-contents": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/markdown-it-table-of-contents/-/markdown-it-table-of-contents-0.4.4.tgz", + "integrity": "sha512-TAIHTHPwa9+ltKvKPWulm/beozQU41Ab+FIefRaQV1NRnpzwcV9QOe6wXQS5WLivm5Q/nlo0rl6laGkMDZE7Gw==", + "dev": true, + "engines": { + "node": ">6.4.0" + } + }, + "node_modules/markdown-it-task-lists": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz", + "integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==", + "dev": true + }, + "node_modules/markdown-it-texmath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-texmath/-/markdown-it-texmath-1.0.0.tgz", + "integrity": "sha512-4hhkiX8/gus+6e53PLCUmUrsa6ZWGgJW2XCW6O0ASvZUiezIK900ZicinTDtG3kAO2kon7oUA/ReWmpW2FByxg==", + "dev": true + }, + "node_modules/markdown-it-video": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/markdown-it-video/-/markdown-it-video-0.6.3.tgz", + "integrity": "sha512-T4th1kwy0OcvyWSN4u3rqPGxvbDclpucnVSSaH3ZacbGsAts964dxokx9s/I3GYsrDCJs4ogtEeEeVP18DQj0Q==", + "dev": true + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/matcher-collection": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/matcher-collection/-/matcher-collection-2.0.1.tgz", + "integrity": "sha512-daE62nS2ZQsDg9raM0IlZzLmI2u+7ZapXBwdoeBUKAYERPDDIc0qNqA8E0Rp2D+gspKR7BgIFP52GeujaGXWeQ==", + "dev": true, + "dependencies": { + "@types/minimatch": "^3.0.3", + "minimatch": "^3.0.2" + }, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/material-icons": { + "version": "1.13.11", + "resolved": "https://registry.npmjs.org/material-icons/-/material-icons-1.13.11.tgz", + "integrity": "sha512-kp2oAdaqo/Zp6hpTZW01rOgDPWmxBUszSdDzkRm1idCjjNvdUMnqu8qu58cll6CObo+o0cydOiPLdoSugLm+mQ==", + "dev": true + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true + }, + "node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dev": true, + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/nan": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz", + "integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==", + "dev": true, + "optional": true + }, + "node_modules/nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha512-A48My/mtCklowHBlI8Fq2jFWK4tX4lJ5E6ytFsSOq1fzpvT0SQSgKhSg7lN5c2uYFOrUAOQp6zhhJnpp1eMloQ==", + "dev": true, + "dependencies": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "dependencies": { + "boolbase": "~1.0.0" + } + }, + "node_modules/nunjucks": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.2.tgz", + "integrity": "sha512-KUi85OoF2NMygwODAy28Lh9qHmq5hO3rBlbkYoC8v377h4l8Pt5qFjILl0LWpMbOrZ18CzfVVUvIHUIrtED3sA==", + "dev": true, + "dependencies": { + "a-sync-waterfall": "^1.0.0", + "asap": "^2.0.3", + "commander": "^5.1.0" + }, + "bin": { + "nunjucks-precompile": "bin/precompile" + }, + "engines": { + "node": ">= 6.9.0" + }, + "optionalDependencies": { + "chokidar": "^3.3.0" + } + }, + "node_modules/nunjucks/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", + "dev": true, + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", + "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", + "dev": true, + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/opn": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-6.0.0.tgz", + "integrity": "sha512-I9PKfIZC+e4RXZ/qr1RhgyCnGgYX0UEIlXgWnCOVACIvFgaC9rz6Won7xbdhoHrd8IIhV7YEpHjreNUNkqCGkQ==", + "deprecated": "The package has been renamed to `open`", + "dev": true, + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", + "dev": true + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", + "dev": true, + "dependencies": { + "through": "~2.3" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, + "node_modules/proxy-middleware": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.15.0.tgz", + "integrity": "sha512-EGCG8SeoIRVMhsqHQUdDigB2i7qU7fCsWASwn54+nPutYO8n4q6EiwMzyfWlC+dzRFExP+kvcnDFdBDHoZBU7Q==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, + "node_modules/query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==", + "dev": true, + "dependencies": { + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", + "dev": true + }, + "node_modules/repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", + "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", + "deprecated": "https://github.com/lydell/resolve-url#deprecated", + "dev": true + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", + "dev": true, + "dependencies": { + "ret": "~0.1.10" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz", + "integrity": "sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/send/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-javascript": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz", + "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==", + "dev": true + }, + "node_modules/simple-git": { + "version": "2.48.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-2.48.0.tgz", + "integrity": "sha512-z4qtrRuaAFJS4PUd0g+xy7aN4y+RvEt/QTJpR184lhJguBA1S/LsVlvE/CM95RsYMOFJG3NGGDjqFCzKU19S/A==", + "dev": true, + "dependencies": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "debug": "^4.3.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/steveukx/" + } + }, + "node_modules/simple-git/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/simple-git/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", + "dev": true, + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dev": true, + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "node_modules/source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "deprecated": "See https://github.com/lydell/source-map-url#deprecated", + "dev": true + }, + "node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", + "dev": true, + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stream-combiner": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", + "integrity": "sha512-6yHMqgLYDzQDcAkL+tjJDC5nSNuNIx0vZtRZeiPh7Saef7VHX9H5Ijn9l2VIol2zaNYlYEX6KyuT/237A58qEQ==", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1", + "through": "~2.3.4" + } + }, + "node_modules/strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-url-auth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-url-auth/-/strip-url-auth-1.0.1.tgz", + "integrity": "sha512-++41PnXftlL3pvI6lpvhSEO+89g1kIJC4MYB5E6yH+WHa5InIqz51yGd1YOGd7VNSNdoEOfzTMqbAM/2PbgaHQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==", + "dev": true + }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/union-value/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unix-crypt-td-js": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/unix-crypt-td-js/-/unix-crypt-td-js-1.1.4.tgz", + "integrity": "sha512-8rMeVYWSIyccIJscb9NdCfZKSRBKYTeVnwmiRYT2ulE3qd1RaDQ0xQDP+rI3ccIWbhu/zuo5cgN8z73belNZgw==", + "dev": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", + "dev": true, + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", + "dev": true, + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", + "deprecated": "Please see https://github.com/lydell/urix#deprecated", + "dev": true + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vue": { + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz", + "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==", + "dev": true + }, + "node_modules/vue-server-renderer": { + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue-server-renderer/-/vue-server-renderer-2.6.14.tgz", + "integrity": "sha512-HifYRa/LW7cKywg9gd4ZtvtRuBlstQBao5ZCWlg40fyB4OPoGfEXAzxb0emSLv4pBDOHYx0UjpqvxpiQFEuoLA==", + "dev": true, + "dependencies": { + "chalk": "^1.1.3", + "hash-sum": "^1.0.2", + "he": "^1.1.0", + "lodash.template": "^4.5.0", + "lodash.uniq": "^4.5.0", + "resolve": "^1.2.0", + "serialize-javascript": "^3.1.0", + "source-map": "0.5.6" + } + }, + "node_modules/vue-server-renderer/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vue-server-renderer/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vue-server-renderer/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/vue-template-compiler": { + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz", + "integrity": "sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g==", + "dev": true, + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.1.0" + } + }, + "node_modules/walk-sync": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/walk-sync/-/walk-sync-2.2.0.tgz", + "integrity": "sha512-IC8sL7aB4/ZgFcGI2T1LczZeFWZ06b3zoHH7jBPyHxOtIIz1jppWHjjEXkOFvFojBVAK9pV7g47xOZ4LW3QLfg==", + "dev": true, + "dependencies": { + "@types/minimatch": "^3.0.3", + "ensure-posix-path": "^1.1.0", + "matcher-collection": "^2.0.0", + "minimatch": "^3.0.4" + }, + "engines": { + "node": "8.* || >= 10.*" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/winston": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.6.tgz", + "integrity": "sha512-J5Zu4p0tojLde8mIOyDSsmLmcP8I3Z6wtwpTDHx1+hGcdhxcJaAmG4CFtagkb+NiN1M9Ek4b42pzMWqfc9jm8w==", + "dev": true, + "dependencies": { + "async": "^3.2.3", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "stack-trace": "0.0.x" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/winston-compat": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/winston-compat/-/winston-compat-0.1.5.tgz", + "integrity": "sha512-EPvPcHT604AV3Ji6d3+vX8ENKIml9VSxMRnPQ+cuK/FX6f3hvPP2hxyoeeCOCFvDrJEujalfcKWlWPvAnFyS9g==", + "dev": true, + "dependencies": { + "cycle": "~1.0.3", + "logform": "^1.6.0", + "triple-beam": "^1.2.0" + }, + "engines": { + "node": ">= 6.4.0" + } + }, + "node_modules/winston-daily-rotate-file": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-3.10.0.tgz", + "integrity": "sha512-KO8CfbI2CvdR3PaFApEH02GPXiwJ+vbkF1mCkTlvRIoXFI8EFlf1ACcuaahXTEiDEKCii6cNe95gsL4ZkbnphA==", + "dev": true, + "dependencies": { + "file-stream-rotator": "^0.4.1", + "object-hash": "^1.3.0", + "semver": "^6.2.0", + "triple-beam": "^1.3.0", + "winston-compat": "^0.1.4", + "winston-transport": "^4.2.0" + }, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "winston": "^2 || ^3" + } + }, + "node_modules/winston-daily-rotate-file/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/winston-transport": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", + "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", + "dev": true, + "dependencies": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 6.4.0" + } + }, + "node_modules/winston-transport/node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "dev": true + }, + "node_modules/winston-transport/node_modules/logform": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.2.tgz", + "integrity": "sha512-W4c9himeAwXEdZ05dQNerhFz2XG80P9Oj0loPUMV23VC2it0orMHQhJm4hdnnor3rd1HsGf6a2lPwBM1zeXHGw==", + "dev": true, + "dependencies": { + "@colors/colors": "1.5.0", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + } + }, + "node_modules/winston-transport/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/winston/node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true + }, + "node_modules/winston/node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + } + }, + "dependencies": { + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true + }, + "@fortawesome/fontawesome-free": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.2.tgz", + "integrity": "sha512-m5cPn3e2+FDCOgi1mz0RexTUvvQibBebOUlUlW0+YrMjDTPkiJ6VTKukA1GRsvRw+12KyJndNjj0O4AgTxm2Pg==", + "dev": true + }, + "@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "dev": true, + "requires": { + "debug": "^4.1.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "dev": true + }, + "@markbind/core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@markbind/core/-/core-5.1.0.tgz", + "integrity": "sha512-YAXjH+qCXnrBzpKIAJkayVLmyIUaG/8Dms3Gpd2VIufeZyW8w0diXdgKSsymjzodTMgghZMdxG3Qpng833ARPg==", + "dev": true, + "requires": { + "@fortawesome/fontawesome-free": "^6.4.0", + "@markbind/core-web": "5.1.0", + "@primer/octicons": "^15.0.1", + "@sindresorhus/slugify": "^0.9.1", + "@tlylt/markdown-it-imsize": "^3.0.0", + "bluebird": "^3.7.2", + "bootswatch": "5.1.3", + "cheerio": "^0.22.0", + "crypto-js": "^4.0.0", + "csv-parse": "^4.14.2", + "ensure-posix-path": "^1.1.1", + "fastmatter": "^2.1.1", + "fs-extra": "^9.0.1", + "gh-pages": "^2.1.1", + "highlight.js": "^10.4.1", + "htmlparser2": "^3.10.1", + "ignore": "^5.1.4", + "js-beautify": "1.14.3", + "katex": "^0.15.6", + "lodash": "^4.17.15", + "markdown-it": "^12.3.2", + "markdown-it-attrs": "^4.1.3", + "markdown-it-emoji": "^1.4.0", + "markdown-it-linkify-images": "^3.0.0", + "markdown-it-mark": "^3.0.0", + "markdown-it-regexp": "^0.4.0", + "markdown-it-sub": "^1.0.0", + "markdown-it-sup": "^1.0.0", + "markdown-it-table-of-contents": "^0.4.4", + "markdown-it-task-lists": "^2.1.1", + "markdown-it-texmath": "^1.0.0", + "markdown-it-video": "^0.6.3", + "material-icons": "^1.9.1", + "moment": "^2.29.4", + "nunjucks": "3.2.2", + "path-is-inside": "^1.0.2", + "simple-git": "^2.17.0", + "url-parse": "^1.5.10", + "uuid": "^8.3.1", + "vue": "2.6.14", + "vue-server-renderer": "2.6.14", + "vue-template-compiler": "2.6.14", + "walk-sync": "^2.0.2", + "winston": "^2.4.4" + } + }, + "@markbind/core-web": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@markbind/core-web/-/core-web-5.1.0.tgz", + "integrity": "sha512-TRzz8ZCr25pylKvFxF/WwXDi4Gbtsb2OLXV61WyTFqVy03tFoEJ2mqncpbliI9DrfDdKWcm1YZPgDCedVkYjKA==", + "dev": true + }, + "@primer/octicons": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-15.2.0.tgz", + "integrity": "sha512-4cHZzcZ3F/HQNL4EKSaFyVsW7XtITiJkTeB1JDDmRuP/XobyWyF9gWxuV9c+byUa8dOB5KNQn37iRvNrIehPUQ==", + "dev": true, + "requires": { + "object-assign": "^4.1.1" + } + }, + "@sindresorhus/slugify": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-0.9.1.tgz", + "integrity": "sha512-b6heYM9dzZD13t2GOiEQTDE0qX+I1GyOotMwKh9VQqzuNiVdPVT8dM43fe9HNb/3ul+Qwd5oKSEDrDIfhq3bnQ==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "lodash.deburr": "^4.1.0" + } + }, + "@tlylt/markdown-it-imsize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@tlylt/markdown-it-imsize/-/markdown-it-imsize-3.0.0.tgz", + "integrity": "sha512-6kTM+vRJTuN2UxNPyJ8yC+NHrzS+MxVHV+z+bDxSr/Fd7eTah2+otLKC2B17YI/1lQnSumA2qokPGuzsA98c6g==", + "dev": true + }, + "@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, + "a-sync-waterfall": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", + "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==", + "dev": true + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "apache-crypt": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/apache-crypt/-/apache-crypt-1.2.5.tgz", + "integrity": "sha512-ICnYQH+DFVmw+S4Q0QY2XRXD8Ne8ewh8HgbuFH4K7022zCxgHM0Hz1xkRnUlEfAXNbwp1Cnhbedu60USIfDxvg==", + "dev": true, + "requires": { + "unix-crypt-td-js": "^1.1.4" + } + }, + "apache-md5": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/apache-md5/-/apache-md5-1.1.7.tgz", + "integrity": "sha512-JtHjzZmJxtzfTSjsCyHgPR155HBe5WGyUyHTaEkfy46qhwCFKx1Epm6nAxgUG3WfUZP1dWhGqj9Z2NOBeZ+uBw==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "dev": true + }, + "async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "bootswatch": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bootswatch/-/bootswatch-5.1.3.tgz", + "integrity": "sha512-NmZFN6rOCoXWQ/PkzmD8FFWDe24kocX9OXWHNVaLxVVnpqpAzEbMFsf8bAfKwVtpNXibasZCzv09B5fLieAh2g==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "cheerio": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", + "integrity": "sha512-8/MzidM6G/TgRelkzDG13y3Y9LxBjCb+8yOEZ9+wwq5gVF2w2pV0wmHvjfT0RvuxGyR7UEuK36r+yYMbT4uKgA==", + "dev": true, + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash.assignin": "^4.0.9", + "lodash.bind": "^4.1.4", + "lodash.defaults": "^4.0.1", + "lodash.filter": "^4.4.0", + "lodash.flatten": "^4.2.0", + "lodash.foreach": "^4.3.0", + "lodash.map": "^4.4.0", + "lodash.merge": "^4.4.0", + "lodash.pick": "^4.2.1", + "lodash.reduce": "^4.4.0", + "lodash.reject": "^4.4.0", + "lodash.some": "^4.4.0" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true + }, + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", + "dev": true + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "crypto-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==", + "dev": true + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha512-dUQOBoqdR7QwV90WysXPLXG5LO7nhYBgiWVfxF80DKPF8zx1t/pUd2FYy73emg3zrjtM6dzmYgbHKfV2rxiHQA==", + "dev": true, + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "dev": true + }, + "csv-parse": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.16.3.tgz", + "integrity": "sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==", + "dev": true + }, + "cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA==", + "dev": true + }, + "de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==", + "dev": true + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + } + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "dev": true, + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "editorconfig": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", + "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", + "dev": true, + "requires": { + "commander": "^2.19.0", + "lru-cache": "^4.1.5", + "semver": "^5.6.0", + "sigmund": "^1.0.1" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + } + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "email-addresses": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", + "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true + }, + "ensure-posix-path": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ensure-posix-path/-/ensure-posix-path-1.1.1.tgz", + "integrity": "sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw==", + "dev": true + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true + }, + "event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", + "dev": true, + "requires": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + }, + "dependencies": { + "split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", + "dev": true, + "requires": { + "through": "2" + } + }, + "stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", + "dev": true, + "requires": { + "duplexer": "~0.1.1" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + } + } + }, + "eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "dev": true + }, + "fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, + "fastmatter": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fastmatter/-/fastmatter-2.1.1.tgz", + "integrity": "sha512-NFrjZEPJZTexoJEuyM5J7n4uFaLf0dOI7Ok4b2IZXOYBqCp1Bh5RskANmQ2TuDsz3M35B1yL2AP/Rn+kp85KeA==", + "dev": true, + "requires": { + "js-yaml": "^3.13.0", + "split": "^1.0.1", + "stream-combiner": "^0.2.2", + "through2": "^3.0.1" + } + }, + "faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "fecha": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", + "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==", + "dev": true + }, + "figlet": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.5.2.tgz", + "integrity": "sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ==", + "dev": true + }, + "file-stream-rotator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.4.1.tgz", + "integrity": "sha512-W3aa3QJEc8BS2MmdVpQiYLKHj3ijpto1gMDlsgCRSKfIUe6MwkcpODGPQ3vZfb0XvCeCqlu9CBQTN7oQri2TZQ==", + "dev": true, + "requires": { + "moment": "^2.11.2" + } + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, + "filename-reserved-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz", + "integrity": "sha512-UZArj7+U+2reBBVCvVmRlyq9D7EYQdUtuNN+1iz7pF1jGcJ2L0TjiRCxsTZfj2xFbM4c25uGCUDpKTHA7L2TKg==", + "dev": true + }, + "filenamify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.1.tgz", + "integrity": "sha512-DKVP0WQcB7WaIMSwDETqImRej2fepPqvXQjaVib7LRZn9Rxn5UbvK2tYTqGf1A1DkIprQQkG4XSQXSOZp7Q3GQ==", + "dev": true, + "requires": { + "filename-reserved-regex": "^1.0.0", + "strip-outer": "^1.0.0", + "trim-repeated": "^1.0.0" + } + }, + "filenamify-url": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/filenamify-url/-/filenamify-url-1.0.0.tgz", + "integrity": "sha512-O9K9JcZeF5VdZWM1qR92NSv1WY2EofwudQayPx5dbnnFl9k0IcZha4eV/FGkjnBK+1irOQInij0yiooCHu/0Fg==", + "dev": true, + "requires": { + "filenamify": "^1.0.0", + "humanize-url": "^1.0.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true + }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", + "dev": true + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "dev": true + }, + "gh-pages": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-2.2.0.tgz", + "integrity": "sha512-c+yPkNOPMFGNisYg9r4qvsMIjVYikJv7ImFOhPIVPt0+AcRUamZ7zkGRLHz7FKB0xrlZ+ddSOJsZv9XAFVXLmA==", + "dev": true, + "requires": { + "async": "^2.6.1", + "commander": "^2.18.0", + "email-addresses": "^3.0.1", + "filenamify-url": "^1.0.0", + "fs-extra": "^8.1.0", + "globby": "^6.1.0" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + } + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", + "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "http-auth": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/http-auth/-/http-auth-3.1.3.tgz", + "integrity": "sha512-Jbx0+ejo2IOx+cRUYAGS1z6RGc6JfYUNkysZM4u4Sfk1uLlGv814F7/PIjQQAuThLdAWxb74JMGd5J8zex1VQg==", + "dev": true, + "requires": { + "apache-crypt": "^1.1.2", + "apache-md5": "^1.0.6", + "bcryptjs": "^2.3.0", + "uuid": "^3.0.0" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } + } + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "dependencies": { + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } + } + }, + "http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "humanize-url": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/humanize-url/-/humanize-url-1.0.1.tgz", + "integrity": "sha512-RtgTzXCPVb/te+e82NDhAc5paj+DuKSratIGAr+v+HZK24eAQ8LMoBGYoL7N/O+9iEc33AKHg45dOMKw3DNldQ==", + "dev": true, + "requires": { + "normalize-url": "^1.0.0", + "strip-url-auth": "^1.0.0" + } + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-core-module": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true + }, + "js-beautify": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.3.tgz", + "integrity": "sha512-f1ra8PHtOEu/70EBnmiUlV8nJePS58y9qKjl4JHfYWlFH6bo7ogZBz//FAZp7jDuXtYnGYKymZPlrg2I/9Zo4g==", + "dev": true, + "requires": { + "config-chain": "^1.1.13", + "editorconfig": "^0.15.3", + "glob": "^7.1.3", + "nopt": "^5.0.0" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "katex": { + "version": "0.15.6", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.15.6.tgz", + "integrity": "sha512-UpzJy4yrnqnhXvRPhjEuLA4lcPn6eRngixW7Q3TJErjg3Aw2PuLFBzTkdUb89UtumxjhHTqL3a5GDGETMSwgJA==", + "dev": true, + "requires": { + "commander": "^8.0.0" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dev": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, + "live-server": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/live-server/-/live-server-1.2.1.tgz", + "integrity": "sha512-Yn2XCVjErTkqnM3FfTmM7/kWy3zP7+cEtC7x6u+wUzlQ+1UW3zEYbbyJrc0jNDwiMDZI0m4a0i3dxlGHVyXczw==", + "dev": true, + "requires": { + "chokidar": "^2.0.4", + "colors": "latest", + "connect": "^3.6.6", + "cors": "latest", + "event-stream": "3.3.4", + "faye-websocket": "0.11.x", + "http-auth": "3.1.x", + "morgan": "^1.9.1", + "object-assign": "latest", + "opn": "latest", + "proxy-middleware": "latest", + "send": "latest", + "serve-index": "^1.9.1" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + } + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + } + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==", + "dev": true + }, + "lodash.assignin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", + "integrity": "sha512-yX/rx6d/UTVh7sSVWVSIMjfnz95evAgDFdb1ZozC35I9mSFCkmzptOzevxjgbQUsc78NR44LVHWjsoMQXy9FDg==", + "dev": true + }, + "lodash.bind": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", + "integrity": "sha512-lxdsn7xxlCymgLYo1gGvVrfHmkjDiyqVv62FAeF2i5ta72BipE1SLxw8hPEPLhD4/247Ijw07UQH7Hq/chT5LA==", + "dev": true + }, + "lodash.deburr": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/lodash.deburr/-/lodash.deburr-4.1.0.tgz", + "integrity": "sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==", + "dev": true + }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "dev": true + }, + "lodash.filter": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", + "integrity": "sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ==", + "dev": true + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true + }, + "lodash.foreach": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==", + "dev": true + }, + "lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==", + "dev": true + }, + "lodash.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==", + "dev": true + }, + "lodash.reject": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", + "integrity": "sha512-qkTuvgEzYdyhiJBx42YPzPo71R1aEr0z79kAv7Ixg8wPFEjgRgJdUsGMG3Hf3OYSF/kHI79XhNlt+5Ar6OzwxQ==", + "dev": true + }, + "lodash.some": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", + "integrity": "sha512-j7MJE+TuT51q9ggt4fSgVqro163BEFjAt3u97IqU+JA2DkWl80nFTrowzLpZ/BnpN7rrl0JA/593NAdd8p/scQ==", + "dev": true + }, + "lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0" + } + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true + }, + "logform": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-1.10.0.tgz", + "integrity": "sha512-em5ojIhU18fIMOw/333mD+ZLE2fis0EzXl1ZwHx4iQzmpQi6odNiY/t+ITNr33JZhT9/KEaH+UPIipr6a9EjWg==", + "dev": true, + "requires": { + "colors": "^1.2.1", + "fast-safe-stringify": "^2.0.4", + "fecha": "^2.3.3", + "ms": "^2.1.1", + "triple-beam": "^1.2.0" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "dev": true + }, + "map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "markbind-cli": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/markbind-cli/-/markbind-cli-5.1.0.tgz", + "integrity": "sha512-6POI1Q++2aZa+Udk/oQ6LX1oNPbKUBDY0mN3Up7VOFeK+XYW51faxuCk2Q91JTBxYRKLNtshxf0y12kB4Cj9Qw==", + "dev": true, + "requires": { + "@markbind/core": "5.1.0", + "@markbind/core-web": "5.1.0", + "bluebird": "^3.7.2", + "chalk": "^3.0.0", + "cheerio": "^0.22.0", + "chokidar": "^3.3.0", + "colors": "1.4.0", + "commander": "^8.1.0", + "figlet": "^1.2.4", + "find-up": "^4.1.0", + "fs-extra": "^9.0.1", + "live-server": "1.2.1", + "lodash": "^4.17.15", + "url-parse": "^1.5.10", + "winston": "^2.4.4", + "winston-daily-rotate-file": "^3.10.0" + } + }, + "markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dev": true, + "requires": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true + } + } + }, + "markdown-it-attrs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/markdown-it-attrs/-/markdown-it-attrs-4.1.6.tgz", + "integrity": "sha512-O7PDKZlN8RFMyDX13JnctQompwrrILuz2y43pW2GagcwpIIElkAdfeek+erHfxUOlXWPsjFeWmZ8ch1xtRLWpA==", + "dev": true, + "requires": {} + }, + "markdown-it-emoji": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-1.4.0.tgz", + "integrity": "sha512-QCz3Hkd+r5gDYtS2xsFXmBYrgw6KuWcJZLCEkdfAuwzZbShCmCfta+hwAMq4NX/4xPzkSHduMKgMkkPUJxSXNg==", + "dev": true + }, + "markdown-it-linkify-images": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-linkify-images/-/markdown-it-linkify-images-3.0.0.tgz", + "integrity": "sha512-Vs5yGJa5MWjFgytzgtn8c1U6RcStj3FZKhhx459U8dYbEE5FTWZ6mMRkYMiDlkFO0j4VCsQT1LT557bY0ETgtg==", + "dev": true, + "requires": { + "markdown-it": "^13.0.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true + }, + "linkify-it": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "dev": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, + "markdown-it": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz", + "integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==", + "dev": true, + "requires": { + "argparse": "^2.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + } + } + }, + "markdown-it-mark": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/markdown-it-mark/-/markdown-it-mark-3.0.1.tgz", + "integrity": "sha512-HyxjAu6BRsdt6Xcv6TKVQnkz/E70TdGXEFHRYBGLncRE9lBFwDNLVtFojKxjJWgJ+5XxUwLaHXy+2sGBbDn+4A==", + "dev": true + }, + "markdown-it-regexp": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/markdown-it-regexp/-/markdown-it-regexp-0.4.0.tgz", + "integrity": "sha512-0XQmr46K/rMKnI93Y3CLXsHj4jIioRETTAiVnJnjrZCEkGaDOmUxTbZj/aZ17G5NlRcVpWBYjqpwSlQ9lj+Kxw==", + "dev": true + }, + "markdown-it-sub": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-sub/-/markdown-it-sub-1.0.0.tgz", + "integrity": "sha512-z2Rm/LzEE1wzwTSDrI+FlPEveAAbgdAdPhdWarq/ZGJrGW/uCQbKAnhoCsE4hAbc3SEym26+W2z/VQB0cQiA9Q==", + "dev": true + }, + "markdown-it-sup": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-sup/-/markdown-it-sup-1.0.0.tgz", + "integrity": "sha512-E32m0nV9iyhRR7CrhnzL5msqic7rL1juWre6TQNxsnApg7Uf+F97JOKxUijg5YwXz86lZ0mqfOnutoryyNdntQ==", + "dev": true + }, + "markdown-it-table-of-contents": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/markdown-it-table-of-contents/-/markdown-it-table-of-contents-0.4.4.tgz", + "integrity": "sha512-TAIHTHPwa9+ltKvKPWulm/beozQU41Ab+FIefRaQV1NRnpzwcV9QOe6wXQS5WLivm5Q/nlo0rl6laGkMDZE7Gw==", + "dev": true + }, + "markdown-it-task-lists": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz", + "integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==", + "dev": true + }, + "markdown-it-texmath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-texmath/-/markdown-it-texmath-1.0.0.tgz", + "integrity": "sha512-4hhkiX8/gus+6e53PLCUmUrsa6ZWGgJW2XCW6O0ASvZUiezIK900ZicinTDtG3kAO2kon7oUA/ReWmpW2FByxg==", + "dev": true + }, + "markdown-it-video": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/markdown-it-video/-/markdown-it-video-0.6.3.tgz", + "integrity": "sha512-T4th1kwy0OcvyWSN4u3rqPGxvbDclpucnVSSaH3ZacbGsAts964dxokx9s/I3GYsrDCJs4ogtEeEeVP18DQj0Q==", + "dev": true + }, + "matcher-collection": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/matcher-collection/-/matcher-collection-2.0.1.tgz", + "integrity": "sha512-daE62nS2ZQsDg9raM0IlZzLmI2u+7ZapXBwdoeBUKAYERPDDIc0qNqA8E0Rp2D+gspKR7BgIFP52GeujaGXWeQ==", + "dev": true, + "requires": { + "@types/minimatch": "^3.0.3", + "minimatch": "^3.0.2" + } + }, + "material-icons": { + "version": "1.13.11", + "resolved": "https://registry.npmjs.org/material-icons/-/material-icons-1.13.11.tgz", + "integrity": "sha512-kp2oAdaqo/Zp6hpTZW01rOgDPWmxBUszSdDzkRm1idCjjNvdUMnqu8qu58cll6CObo+o0cydOiPLdoSugLm+mQ==", + "dev": true + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + } + }, + "moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "dev": true + }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dev": true, + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "nan": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz", + "integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true + }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha512-A48My/mtCklowHBlI8Fq2jFWK4tX4lJ5E6ytFsSOq1fzpvT0SQSgKhSg7lN5c2uYFOrUAOQp6zhhJnpp1eMloQ==", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + } + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, + "nunjucks": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.2.tgz", + "integrity": "sha512-KUi85OoF2NMygwODAy28Lh9qHmq5hO3rBlbkYoC8v377h4l8Pt5qFjILl0LWpMbOrZ18CzfVVUvIHUIrtED3sA==", + "dev": true, + "requires": { + "a-sync-waterfall": "^1.0.0", + "asap": "^2.0.3", + "chokidar": "^3.3.0", + "commander": "^5.1.0" + }, + "dependencies": { + "commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true + } + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-hash": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", + "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "opn": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-6.0.0.tgz", + "integrity": "sha512-I9PKfIZC+e4RXZ/qr1RhgyCnGgYX0UEIlXgWnCOVACIvFgaC9rz6Won7xbdhoHrd8IIhV7YEpHjreNUNkqCGkQ==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", + "dev": true, + "requires": { + "through": "~2.3" + } + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", + "dev": true + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, + "proxy-middleware": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.15.0.tgz", + "integrity": "sha512-EGCG8SeoIRVMhsqHQUdDigB2i7qU7fCsWASwn54+nPutYO8n4q6EiwMzyfWlC+dzRFExP+kvcnDFdBDHoZBU7Q==", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, + "query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==", + "dev": true, + "requires": { + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", + "dev": true + }, + "repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "resolve": { + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", + "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "dev": true, + "requires": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", + "dev": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safe-stable-stringify": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz", + "integrity": "sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==", + "dev": true + }, + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz", + "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + } + } + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + } + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==", + "dev": true + }, + "simple-git": { + "version": "2.48.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-2.48.0.tgz", + "integrity": "sha512-z4qtrRuaAFJS4PUd0g+xy7aN4y+RvEt/QTJpR184lhJguBA1S/LsVlvE/CM95RsYMOFJG3NGGDjqFCzKU19S/A==", + "dev": true, + "requires": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "debug": "^4.3.2" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", + "dev": true, + "requires": { + "is-plain-obj": "^1.0.0" + } + }, + "source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "dev": true + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "requires": { + "through": "2" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true + }, + "stream-combiner": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", + "integrity": "sha512-6yHMqgLYDzQDcAkL+tjJDC5nSNuNIx0vZtRZeiPh7Saef7VHX9H5Ijn9l2VIol2zaNYlYEX6KyuT/237A58qEQ==", + "dev": true, + "requires": { + "duplexer": "~0.1.1", + "through": "~2.3.4" + } + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", + "dev": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, + "strip-url-auth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-url-auth/-/strip-url-auth-1.0.1.tgz", + "integrity": "sha512-++41PnXftlL3pvI6lpvhSEO+89g1kIJC4MYB5E6yH+WHa5InIqz51yGd1YOGd7VNSNdoEOfzTMqbAM/2PbgaHQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true + }, + "trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==", + "dev": true + }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + } + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, + "unix-crypt-td-js": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/unix-crypt-td-js/-/unix-crypt-td-js-1.1.4.tgz", + "integrity": "sha512-8rMeVYWSIyccIJscb9NdCfZKSRBKYTeVnwmiRYT2ulE3qd1RaDQ0xQDP+rI3ccIWbhu/zuo5cgN8z73belNZgw==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", + "dev": true + } + } + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", + "dev": true + }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true + }, + "vue": { + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz", + "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==", + "dev": true + }, + "vue-server-renderer": { + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue-server-renderer/-/vue-server-renderer-2.6.14.tgz", + "integrity": "sha512-HifYRa/LW7cKywg9gd4ZtvtRuBlstQBao5ZCWlg40fyB4OPoGfEXAzxb0emSLv4pBDOHYx0UjpqvxpiQFEuoLA==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "hash-sum": "^1.0.2", + "he": "^1.1.0", + "lodash.template": "^4.5.0", + "lodash.uniq": "^4.5.0", + "resolve": "^1.2.0", + "serialize-javascript": "^3.1.0", + "source-map": "0.5.6" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true + } + } + }, + "vue-template-compiler": { + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz", + "integrity": "sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g==", + "dev": true, + "requires": { + "de-indent": "^1.0.2", + "he": "^1.1.0" + } + }, + "walk-sync": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/walk-sync/-/walk-sync-2.2.0.tgz", + "integrity": "sha512-IC8sL7aB4/ZgFcGI2T1LczZeFWZ06b3zoHH7jBPyHxOtIIz1jppWHjjEXkOFvFojBVAK9pV7g47xOZ4LW3QLfg==", + "dev": true, + "requires": { + "@types/minimatch": "^3.0.3", + "ensure-posix-path": "^1.1.0", + "matcher-collection": "^2.0.0", + "minimatch": "^3.0.4" + } + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true + }, + "winston": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.6.tgz", + "integrity": "sha512-J5Zu4p0tojLde8mIOyDSsmLmcP8I3Z6wtwpTDHx1+hGcdhxcJaAmG4CFtagkb+NiN1M9Ek4b42pzMWqfc9jm8w==", + "dev": true, + "requires": { + "async": "^3.2.3", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "stack-trace": "0.0.x" + }, + "dependencies": { + "async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "dev": true + } + } + }, + "winston-compat": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/winston-compat/-/winston-compat-0.1.5.tgz", + "integrity": "sha512-EPvPcHT604AV3Ji6d3+vX8ENKIml9VSxMRnPQ+cuK/FX6f3hvPP2hxyoeeCOCFvDrJEujalfcKWlWPvAnFyS9g==", + "dev": true, + "requires": { + "cycle": "~1.0.3", + "logform": "^1.6.0", + "triple-beam": "^1.2.0" + } + }, + "winston-daily-rotate-file": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-3.10.0.tgz", + "integrity": "sha512-KO8CfbI2CvdR3PaFApEH02GPXiwJ+vbkF1mCkTlvRIoXFI8EFlf1ACcuaahXTEiDEKCii6cNe95gsL4ZkbnphA==", + "dev": true, + "requires": { + "file-stream-rotator": "^0.4.1", + "object-hash": "^1.3.0", + "semver": "^6.2.0", + "triple-beam": "^1.3.0", + "winston-compat": "^0.1.4", + "winston-transport": "^4.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "winston-transport": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", + "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", + "dev": true, + "requires": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + }, + "dependencies": { + "fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "dev": true + }, + "logform": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.2.tgz", + "integrity": "sha512-W4c9himeAwXEdZ05dQNerhFz2XG80P9Oj0loPUMV23VC2it0orMHQhJm4hdnnor3rd1HsGf6a2lPwBM1zeXHGw==", + "dev": true, + "requires": { + "@colors/colors": "1.5.0", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + } + } +} diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 00000000000..aa7083fd8a7 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,14 @@ +{ + "name": "docs", + "version": "1.0.0", + "description": "AB-3 docs", + "scripts": { + "init": "markbind init", + "build": "markbind build", + "serve": "markbind serve", + "deploy": "markbind deploy" + }, + "devDependencies": { + "markbind-cli": "^5.1.0" + } +} diff --git a/docs/site.json b/docs/site.json new file mode 100644 index 00000000000..3f7eaae931e --- /dev/null +++ b/docs/site.json @@ -0,0 +1,29 @@ +{ + "baseUrl": "", + "titlePrefix": "MedBook", + "titleSuffix": "AddressBook Level-3", + "faviconPath": "images/SeEduLogo.png", + "style": { + "codeTheme": "light" + }, + "ignore": [ + "_markbind/layouts/*", + "_markbind/logs/*", + "_site/*", + "site.json", + "*.md", + "*.njk", + ".git/*", + "node_modules/*" + ], + "pagesExclude": ["node_modules/*"], + "pages": [ + { + "glob": ["**/index.md", "**/*.md"] + } + ], + "deploy": { + "message": "Site Update." + }, + "timeZone": "Asia/Singapore" +} diff --git a/docs/stylesheets/main.css b/docs/stylesheets/main.css new file mode 100644 index 00000000000..1074ade42dd --- /dev/null +++ b/docs/stylesheets/main.css @@ -0,0 +1,144 @@ +mark { + background-color: #ff0; + border-radius: 5px; + padding-top: 0; + padding-bottom: 0; +} + +.indented { + padding-left: 20px; +} + +.theme-card img { + width: 100%; +} + +/* Scrollbar */ + +.slim-scroll::-webkit-scrollbar { + width: 5px; +} + +.slim-scroll::-webkit-scrollbar-thumb { + background: #808080; + border-radius: 20px; +} + +.slim-scroll::-webkit-scrollbar-track { + background: transparent; + border-radius: 20px; +} + +.slim-scroll-blue::-webkit-scrollbar { + width: 5px; +} + +.slim-scroll-blue::-webkit-scrollbar-thumb { + background: #00b0ef; + border-radius: 20px; +} + +.slim-scroll-blue::-webkit-scrollbar-track { + background: transparent; + border-radius: 20px; +} + +/* Layout containers */ + +#flex-body { + display: flex; + flex: 1; + align-items: start; +} + +#content-wrapper { + flex: 1; + margin: 0 auto; + min-width: 0; + max-width: 1000px; + overflow-x: auto; + padding: 0.8rem 20px 0 20px; + transition: 0.4s; + -webkit-transition: 0.4s; +} + +#site-nav, +#page-nav { + display: flex; + flex-direction: column; + position: sticky; + top: var(--sticky-header-height); + flex: 0 0 auto; + max-width: 300px; + max-height: calc(100vh - var(--sticky-header-height)); + width: 300px; +} + +#site-nav { + border-right: 1px solid lightgrey; + padding-bottom: 20px; + z-index: 999; +} + +.site-nav-top { + margin: 0.8rem 0; + padding: 0 12px 12px 12px; +} + +.nav-component { + overflow-y: auto; +} + +#page-nav { + border-left: 1px solid lightgrey; +} + +@media screen and (max-width: 1299.98px) { + #page-nav { + display: none; + } +} + +/* Bootstrap medium(md) responsive breakpoint */ +@media screen and (max-width: 991.98px) { + #site-nav { + display: none; + } +} + +/* Bootstrap small(sm) responsive breakpoint */ +@media (max-width: 767.98px) { + .indented { + padding-left: 10px; + } + + #content-wrapper { + padding: 0 10px; + } +} + +/* Bootstrap extra small(xs) responsive breakpoint */ +@media screen and (max-width: 575.98px) { + #site-nav { + display: none; + } +} + +/* Hide site navigation when printing */ +@media print { + #site-nav { + display: none; + } + + #page-nav { + display: none; + } +} + +h2, +h3, +h4, +h5, +h6 { + color: #e46c0a; +} diff --git a/docs/team/adammangzijun.md b/docs/team/adammangzijun.md new file mode 100644 index 00000000000..639d944e4ed --- /dev/null +++ b/docs/team/adammangzijun.md @@ -0,0 +1,61 @@ +--- +layout: default.md +title: "Adam's Project Portfolio Page" +--- + +### Project: MedBook + +## Overview: + +MedBook is a desktop application crafted specifically for doctors and medical administrative assistants of private clinics. It offers an intuitive and efficient interface for seamless management of patient details and medical records, enabling healthcare professionals to easily monitor and access patient information. + +## Summary of Contributions + +- Code contributed: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=adammangzijun&breakdown=true) +- Enhancements implemented: + + - **Edit Patient Details Feature**: + Updated the edit functionality within MedBook, enabling users to edit the additional details of a certain patients as well. + + - **Edit Record Details Feature**: + Developed a new functionality within MedBook, enabling users edit details of patient records. + +- Contributions to the User Guide (UG): + + - Added new sections explaining the usage of the `editpatient` and `editrecord` commands I've implemented. + - Touched up on the feature summary table. + - Tidied, sorted and double-checked UG for PE-D, ensuring readability. + - Reviewed the UG together as a group for final submission + +- Contributions to the Developer Guide (DG): + - Added implementation details for `editpatient` command. + - Added implementation details for `editrecord` command. + - Added `editpatient` and `editrecord` sequence diagram. + - Reviewed the DG together as a group for final submission + +## Contributions to Team-Based Tasks + +- Review/mentoring contributions: + - Some links to PRs reviewed. [#26](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/26) , [#36](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/36), [#64](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/64) + - Helped to identify potential bugs through questions prompting. For instance, I've successfully identified a bug in the file attachment command when my teammate first implemented it. Provided some possible suggestions to resolve the issue. + - Assisted in the explanation of certain parts of the code such as how to enforce non-duplication of prefixes in the command. + +## Contributions Beyond the Project Team + +- Evidence of helping others: + - Bugs reported in other team's products. + - Raised technical issues in the forum which may apply to others as well. +- Evidence of technical leadership: + - Sharing useful information in the forum. + +## Project Effort Explanation +- Depth of Enhancements: + - The enhancements I implemented required a deep understanding of MedBook’s existing architecture. For example, the Edit Patient Details Feature not only involved interfacing with the GUI components but also necessitated intricate handling of data validation and error-checking to maintain data integrity. It was essential to ensure that edited patient details were consistent across all instances of the application, requiring careful consideration of concurrent data access and potential race conditions. + +- Complexity of Implementation: + - The Edit Record Details Feature introduced a new layer of complexity as it required manipulating nested data structures. It was not just about updating fields but also about ensuring that related records remained in sync. Crafting this feature demanded a thorough understanding of how records were interrelated and the potential cascading effects of any changes. + +- Completion Criteria: + - Enhancements are considered complete when they have been rigorously tested, reviewed by peers, and all identified bugs have been resolved. For instance, the edit functionalities underwent multiple cycles of code reviews and were subjected to both unit and integration tests. They have been stress-tested to handle edge cases, ensuring the application's robustness. +- Impact of Enhancements: + - The enhancements have streamlined the workflow for end-users, reducing the number of steps needed to update patient and record details significantly. By refining the user journey, healthcare providers can now access and modify data with reduced effort and increased accuracy, ultimately facilitating better patient management. diff --git a/docs/team/clin-lyx.md b/docs/team/clin-lyx.md new file mode 100644 index 00000000000..659d6c780bf --- /dev/null +++ b/docs/team/clin-lyx.md @@ -0,0 +1,54 @@ +--- +layout: default.md +title: "Lin Yuxiang's Project Portfolio Page" +--- + +### Project Medbook + +MedBook is a desktop application crafted specifically for doctors and medical administrative assistants of private clinics. It offers an intuitive and efficient interface for seamless management of patient details and medical records, enabling healthcare professionals to easily monitor and access patient information. + +Given below are my contributions to the project. + +- **New Feature**: Added the ability to view patient's medical records. + + - What it does: allows the user to view medical records of different patient. + - Justification: This feature improves the product significantly because a user can visualize the medical records. + - Highlights: This enhancement affects existing ui layout. The implementation was challenging as it required changes to existing commands, creation of new classes, formatting of the visuals of medical record card and reformatting the overall ui layout. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=clin-lyx&breakdown=false&sort=groupTitle%20dsc&sortWithin=title&since=2023-09-22&timeframe=commit&mergegroup=&groupSelect=groupByRepos) + +* **Enhancements to existing features**: + - Updated the Patient Card formatting(Pull request [\#35](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/35)) + - Updated the help page, containing basic information of available commands and making the helpWindow scrollable. (Pull request [\#80](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/80), [\#81](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/81), [\#92](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/92)) + +* **Documentation**: + + - User Guide: + - Refine introduction. Adding more detail to the introduction as well as sections, such as Glossary, Navigating the GUI and tutorial.(Pull Request: [\#92](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/92)) + - Update description of feature `list` and `view` (Pull Request: [\#63](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/63)) + - Add UI component pictures. + - Tested the validity of constraints for the parameters + - Add more detail to the user guide to make it more user-centric. + - Reviewed the UG together as a group for final submission + - Developer Guide: + - Added implementation details of the `view` feature. (Pull Request: [\#71](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/71)) + - Created a sequence diagram of the execution of view command. + - Create a more up-to-date user story table. + - Fix the issues in the use cases. + - Reviewed the DG together as a group for final submission + +- **Project management**: + - Assessed testers' issues from PE-D + +* **Community**: + - PRs reviewed: [\#23](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/23), [\#31](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/31), + [\#41](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/41), [\#54](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/54), + [\#61](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/61), [\#66](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/66), + [\#70](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/70), [\#82](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/82), + [\#91](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/91), [\#93](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/93), + [\#95](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/95), [\#149](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/149), + [\#151](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/151), [\#157](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/157), + [\#160](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/160), [\#162](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/162), + [\#166](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/166), [\#171](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/171), + [\#175](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/175), [\#176](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/176) + - Reported 8 bugs for the product, D.A.V.E., during PE-D. diff --git a/docs/team/darren159.md b/docs/team/darren159.md new file mode 100644 index 00000000000..cc8c12078c7 --- /dev/null +++ b/docs/team/darren159.md @@ -0,0 +1,65 @@ +--- + layout: default.md + title: "Darren's Project Portfolio Page" +--- + +### Project: MedBook + +#### Overview + +MedBook is a desktop application crafted specifically for doctors and medical administrative assistants of private clinics. It offers an intuitive and efficient interface for seamless management of patient details and medical records, enabling healthcare professionals to easily monitor and access patient information. + +#### Summary of Contributions + +- **Code contributed**: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=darren159&breakdown=false&sort=groupTitle%20dsc&sortWithin=title&since=2023-09-22&timeframe=commit&mergegroup=&groupSelect=groupByRepos) + +- **Enhancements Implemented**: + + - **Pin Feature**: + + - Developed a new functionality within MedBook, enabling users to prioritize certain patients by pinning them to the specially designed **Pinned Patient List**. The enhancement comes alongside two commands, `pin` and `unpin`, for users to control the pinning of patients. + - The enhancement required the creation of a separate UI component to display the pinned patients. + - An `isPinned` field was added to each patient to control whether it would appear in the **Pinned Patient List**. + - Two commands, `pin` and `unpin` were created to toggle the `isPinned` status of the specific patient + + - **Appointments Feature**: + - Developed a new functionality within MedBook, enabling users to track and manage patient appointments. This feature includes a dedicated window displaying an **Appointment List** and an integrated calendar, which displays scheduled appointments for the respective dates. The enhancement comes alongside three commands `addappointment`, `deleteappointment` and `viewappointment`. + - The enhancement required the creation of a seperate UI component to display the appointments. A new window was created to allow for more space to display the appointments. A calendar was also created manually using a JavaFX `GridPane`. + - A new class `Appointment` was created to contain information about the appointment. A new class `UniqueAppointmentList` was created alongside to hold the `Appointment` objects. The `UniqueAppointmentList` was then added to each patient to associate each `Appointment` with a patient. + - The commands, `addappointment` and `deleteappointment` were created to allow the addition and deletion of `Appointment` objects. The `viewappointment` command was created to allow users to open up the **Appointment Window** and view the appointments. + - A custom method was also created to populate the calendar with appointments. + +- **Contributions to the UG**: + + - Added new sections explaining the usage of the `addappointment`, `deleteappointment`, `viewappointment`, `pin`, and `unpin` commands which I have implemented. + - Added **Tips** and **Notes** sections to make the UG more reader-friendly. + - Enhanced the tone of the UG with a more user-centric tone, e.g. you-language. + +- **Contributions to the DG**: + + - Added new sections explaining the implementation of the **Appointments Feature** and **Pin Feature** as well as the commands related to it. + - Created Sequence and Class Diagrams to aid in the explanation of the above implementations. + - Added Use Cases as well as Instructions for Manual Testing for the above features. + +- **Contributions to team-based tasks**: + + 1. Setting up the GitHub team org/repo + 2. Setting up of CodeCov + 3. Removing traces of AddressBook Level-3 and renaming to MedBook + 4. Maintaining the issue tracker + 5. Release management + 6. Updating index.md and README.md + +- **Review/mentoring contributions**: + + - **PRs reviewed**: + [\#20](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/20), [\#21](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/21), + [\#32](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/32), [\#35](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/35), + [\#36](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/36), [\#37](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/37), + [\#40](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/40), [\#42](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/42), + [\#68](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/68), [\#71](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/71), + [\#79](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/79), [\#80](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/80), [\#81](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/81), + [\#86](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/86), [\#150](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/150), [\#152](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/152), [\#159](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/159), [\#163](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/163), [\#165](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/165), [\#167](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/167), [\#172](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/172) + +- **Contributions beyond the project team**: + - Reported 16 bugs for the product, HealthSync, during PE-D. diff --git a/docs/team/hjoneweek.md b/docs/team/hjoneweek.md new file mode 100644 index 00000000000..535f03e8b7e --- /dev/null +++ b/docs/team/hjoneweek.md @@ -0,0 +1,52 @@ +--- +layout: default.md +title: "Hanjoo's Project Portfolio Page" +--- + +### Project: MedBook + +**Overview**: MedBook is a desktop application crafted specifically for doctors and medical administrative assistants of private clinics. It offers an intuitive and efficient interface for seamless management of patient details and medical records, enabling healthcare professionals to easily monitor and access patient information. + +**Contributions**: Click [here](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=hjoneweek&breakdown=false&sort=groupTitle%20dsc&sortWithin=title&since=2023-09-22&timeframe=commit&mergegroup=&groupSelect=groupByRepos) to view my contribution to the project in RepoSense. + +- **Enhancement 1**: Person Class and Add Patient Feature [#31](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/31) + - Added more attributes appropriate to hospital/clinic settings to the `Person` class. Updated attributes include `BloodType`, `Age`, `Gender`, and `Allergy`. + - The enhancement includes more essential information about `Person` for doctors and medical staffs to have a deeper understanding about their patients. + +- **New Feature 1**: Record Class and Add Record Feature [#36](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/36) [#68](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/68) + - An essential feature that allows doctors to add medical records to patients after their visit to the clinic. A `Record` includes information about the date and time of the visit, the conditions of the patient, and the prescribed medications for the patient. + - Created the `Record` class and `UniqueRecordList` class to ensure uniqueness of each `Record`. + - Created the `DateTime`, `Condition`, and `Medication` classes, which are attributes of the `Record` class. + - Created the `AddRecordCommandParser` class and the `AddRecordCommand` class to execute the `addrecord` command. + +- **New Feature 2**: Search Record Feature [#86](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/86) + - Another essential feature that allows doctors to use keywords to search for the past records of the patient that they are currently viewing. + - Created the `RecordContainsKeywordPredicate` class, `FindRecordCommandParser` class, and the `FindRecordCommand` class to execute the `searchrecord` command. + +- **New Feature 3** Delete Record Feature [#88](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/88) + - Allows doctors to delete records of patients if necessary. + - Created the `DeleteRecordCommandParser` class and the `DeleteRecordCommand` class to execute the `deleterecord` command. + +- **Documentation** + - User Guide + - Added new sections explaining how to use the newly implemented features and commands, which are `addrecord`, `searchrecord`, and `deleterecord` command. + - Modified `addpatient` command (which was originally `add` command) section according to the updated attributes of the patient. + - Reviewed the User Guide altogether as a team. + + - Developer Guide + - Added the **Record Feature** section (except for the implementation details of the `editrecord` command). + - Created the sequence diagrams of the executions of the `addrecord`, `searchrecord`, `deleterecord`, and `addpatient` commands. + - Created the class diagrams of the `Record`, and `Person` classes. + - Updated the class diagrams for `UI`, `Storage`, and `Model` components of MedBook. + - Reviewed the Developer Guide altogether as a team. + +- **Community** + - PRs reviewed: [#24](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/24), [#92](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/92), + [#96](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/96), [#148](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/148), + [#155](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/155), [#156](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/156), + [#158](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/158), [#164](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/164), + [#166](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/166), [#169](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/169), + [#171](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/171), [#174](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/174), + [#177](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/177), [#178](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/178), + [#179](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/179), [#181](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/181) + - Reported bugs and suggestions for other teams in the class by participating in the PE-D. Click [here](https://github.com/hjoneweek/ped/issues) to view the issues I posted for the project I reviewed. diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index 773a07794e2..00000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -layout: page -title: John Doe's Project Portfolio Page ---- - -### Project: AddressBook Level 3 - -AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - -Given below are my contributions to the project. - -* **New Feature**: Added the ability to undo/redo previous commands. - * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. - * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. - * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. - * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}* - -* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. - -* **Code contributed**: [RepoSense link]() - -* **Project management**: - * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub - -* **Enhancements to existing features**: - * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) - * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) - -* **Documentation**: - * User Guide: - * Added documentation for the features `delete` and `find` [\#72]() - * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() - * Developer Guide: - * Added implementation details of the `delete` feature. - -* **Community**: - * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() - * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]()) - * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]()) - * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]()) - -* **Tools**: - * Integrated a third party library (Natty) to the project ([\#42]()) - * Integrated a new Github plugin (CircleCI) to the team repo - -* _{you can add/remove categories in the list above}_ diff --git a/docs/team/ryanongwx.md b/docs/team/ryanongwx.md new file mode 100644 index 00000000000..68cfa177a34 --- /dev/null +++ b/docs/team/ryanongwx.md @@ -0,0 +1,53 @@ +--- + layout: default.md + title: "Ryan's Project Portfolio Page" +--- + +### Project Medbook + +**Overview**: MedBook is a desktop application crafted specifically for doctors and medical administrative assistants of private clinics. It offers an intuitive and efficient interface for seamless management of patient details and medical records, enabling healthcare professionals to easily monitor and access patient information. + +- **Code contributed**: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=ryanongwx&breakdown=false&sort=groupTitle%20dsc&sortWithin=title&since=2023-09-22&timeframe=commit&mergegroup=&groupSelect=groupByRepos) + +Given below are my contributions to the project. + +- **Enhancement 1**: Search Patient v1.2 + + - Broaden the scope of search such that it covers all the necessary information stored within all patients. This is to ensure that a doctor could search for any information on a patient when required. This is especially important in situations where doctors would like to search for all patients which have a certain condition or have a certain allergy. + +- **New Feature 1**: Add Attach File Functionality v1.3 + + - Allows doctors to attach a medical report documennt to a record. This allows efficient management of medical documents and allows doctors to keep track of their records in an organised manner. + - Files would be stored in their local storage of the device. Upon attaching the file, they would be able to open the document whenever they require it. + - This is extremely useful for attaching files such as blood test results that is vital to be kept together with patient records. + +- **New Feature 2**: Add Encryption/Decryption Functionality v1.3 + + - Encrypts data from the application using AES encryption such that the data in the addressbook.json file is encrypted. + - This is to ensure privacy and confidentiality for patient data. + - This functionality was later scrapped due to the group prject constraints + +- **Bug Fixes**: Fixed all Bugs in repo for v1.2 + + - [Link to Bug Fix](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/40) + - Prior to this all Java CI checks failed due to failing of testcases and some features not implemented correctly + - Fixed wrong feature implementations wrote by teammates in addrecord, addpatient and pin patient commands. + - Fixed all testcases such that all Java CI checks could pass in in addrecord, addpatient and pin patient commands. + +- **Project management**: + + - Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub + - Filmed and stitched the video demo together + - Assessed testers' issues from PE-D + +- **Documentation**: + + - User Guide: + - Added documentation for the all features that I had implemented + - Developer Guide: + - Added implementation details for the all features that I had implemented + +- **Community**: + - PRs reviewed : [\#28](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/28), [\#62](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/62), [\152](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/152), [\#56](https://github.com/AY2324S1-CS2103T-T12-4/tp/pull/56) + - Contributed to forum discussions (examples: [\#389](https://github.com/nus-cs2103-AY2324S1/forum/issues/389) and [\#355](https://github.com/nus-cs2103-AY2324S1/forum/issues/355)) + - Reported bugs and suggestions for other teams in the class (examples: [\#184](https://github.com/AY2324S1-CS2103-T16-2/tp/issues/184), [\#183](https://github.com/AY2324S1-CS2103-T16-2/tp/issues/183), [\#173](https://github.com/AY2324S1-CS2103-T16-2/tp/issues/173)) diff --git a/docs/tutorials/AddRemark.md b/docs/tutorials/AddRemark.md index d98f38982e7..8b18f27946b 100644 --- a/docs/tutorials/AddRemark.md +++ b/docs/tutorials/AddRemark.md @@ -1,8 +1,11 @@ --- -layout: page -title: "Tutorial: Adding a command" + layout: default.md + title: "Tutorial: Adding a command" + pageNav: 3 --- +# Tutorial: Adding a command + Let's walk you through the implementation of a new command — `remark`. This command allows users of the AddressBook application to add optional remarks to people in their address book and edit it if required. The command should have the following format: @@ -22,7 +25,7 @@ For now, let’s keep `RemarkCommand` as simple as possible and print some outpu **`RemarkCommand.java`:** -``` java +```java package seedu.address.logic.commands; import seedu.address.model.Model; @@ -57,13 +60,13 @@ Run `Main#main` and try out your new `RemarkCommand`. If everything went well, y While we have successfully printed a message to `ResultDisplay`, the command does not do what it is supposed to do. Let’s change the command to throw a `CommandException` to accurately reflect that our command is still a work in progress. -![The relationship between RemarkCommand and Command](../images/add-remark/RemarkCommandClass.png) + Following the convention in other commands, we add relevant messages as constants and use them. **`RemarkCommand.java`:** -``` java +```java public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the remark of the person identified " + "by the index number used in the last person listing. " @@ -90,7 +93,7 @@ Let’s change `RemarkCommand` to parse input from the user. We start by modifying the constructor of `RemarkCommand` to accept an `Index` and a `String`. While we are at it, let’s change the error message to echo the values. While this is not a replacement for tests, it is an obvious way to tell if our code is functioning as intended. -``` java +```java import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; //... public class RemarkCommand extends Command { @@ -142,13 +145,13 @@ Now let’s move on to writing a parser that will extract the index and remark f Create a `RemarkCommandParser` class in the `seedu.address.logic.parser` package. The class must extend the `Parser` interface. -![The relationship between Parser and RemarkCommandParser](../images/add-remark/RemarkCommandParserClass.png) + Thankfully, `ArgumentTokenizer#tokenize()` makes it trivial to parse user input. Let’s take a look at the JavaDoc provided for the function to understand what it does. **`ArgumentTokenizer.java`:** -``` java +```java /** * Tokenizes an arguments string and returns an {@code ArgumentMultimap} * object that maps prefixes to their respective argument values. Only the @@ -166,7 +169,7 @@ We can tell `ArgumentTokenizer#tokenize()` to look out for our new prefix `r/` a **`ArgumentMultimap.java`:** -``` java +```java /** * Returns the last value of {@code prefix}. */ @@ -181,7 +184,7 @@ This appears to be what we need to get a String of the remark. But what about th **`DeleteCommandParser.java`:** -``` java +```java Index index = ParserUtil.parseIndex(args); return new DeleteCommand(index); ``` @@ -192,7 +195,7 @@ Now that we have the know-how to extract the data that we need from the user’s **`RemarkCommandParser.java`:** -``` java +```java public RemarkCommand parse(String args) throws ParseException { requireNonNull(args); ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, @@ -212,11 +215,11 @@ public RemarkCommand parse(String args) throws ParseException { } ``` -
    + -:information_source: Don’t forget to update `AddressBookParser` to use our new `RemarkCommandParser`! +Don’t forget to update `AddressBookParser` to use our new `RemarkCommandParser`! -
    + If you are stuck, check out the sample [here](https://github.com/se-edu/addressbook-level3/commit/dc6d5139d08f6403da0ec624ea32bd79a2ae0cbf#diff-8bf239e8e9529369b577701303ddd96af93178b4ed6735f91c2d8488b20c6b4a). @@ -244,7 +247,7 @@ Simply add the following to [`seedu.address.ui.PersonCard`](https://github.com/s **`PersonCard.java`:** -``` java +```java @FXML private Label remark; ``` @@ -276,11 +279,11 @@ We change the constructor of `Person` to take a `Remark`. We will also need to d Unfortunately, a change to `Person` will cause other commands to break, you will have to modify these commands to use the updated `Person`! -
    + -:bulb: Use the `Find Usages` feature in IntelliJ IDEA on the `Person` class to find these commands. +Use the `Find Usages` feature in IntelliJ IDEA on the `Person` class to find these commands. -
    + Refer to [this commit](https://github.com/se-edu/addressbook-level3/commit/ce998c37e65b92d35c91d28c7822cd139c2c0a5c) and check that you have got everything in order! @@ -291,11 +294,11 @@ AddressBook stores data by serializing `JsonAdaptedPerson` into `json` with the While the changes to code may be minimal, the test data will have to be updated as well. -
    + -:exclamation: You must delete AddressBook’s storage file located at `/data/addressbook.json` before running it! Not doing so will cause AddressBook to default to an empty address book! +You must delete AddressBook’s storage file located at `/data/addressbook.json` before running it! Not doing so will cause AddressBook to default to an empty address book! -
    + Check out [this commit](https://github.com/se-edu/addressbook-level3/commit/556cbd0e03ff224d7a68afba171ad2eb0ce56bbf) to see what the changes entail. @@ -308,7 +311,7 @@ Just add [this one line of code!](https://github.com/se-edu/addressbook-level3/c **`PersonCard.java`:** -``` java +```java public PersonCard(Person person, int displayedIndex) { //... remark.setText(person.getRemark().value); @@ -328,7 +331,7 @@ save it with `Model#setPerson()`. **`RemarkCommand.java`:** -``` java +```java //... public static final String MESSAGE_ADD_REMARK_SUCCESS = "Added remark to Person: %1$s"; public static final String MESSAGE_DELETE_REMARK_SUCCESS = "Removed remark from Person: %1$s"; diff --git a/docs/tutorials/RemovingFields.md b/docs/tutorials/RemovingFields.md index f29169bc924..c73bd379e5e 100644 --- a/docs/tutorials/RemovingFields.md +++ b/docs/tutorials/RemovingFields.md @@ -1,8 +1,11 @@ --- -layout: page -title: "Tutorial: Removing Fields" + layout: default.md + title: "Tutorial: Removing Fields" + pageNav: 3 --- +# Tutorial: Removing Fields + > Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away. > > — Antoine de Saint-Exupery @@ -10,17 +13,17 @@ title: "Tutorial: Removing Fields" When working on an existing code base, you will most likely find that some features that are no longer necessary. This tutorial aims to give you some practice on such a code 'removal' activity by removing the `address` field from `Person` class. -
    + **If you have done the [Add `remark` command tutorial](AddRemark.html) already**, you should know where the code had to be updated to add the field `remark`. From that experience, you can deduce where the code needs to be changed to _remove_ that field too. The removing of the `address` field can be done similarly.

    However, if you have no such prior knowledge, removing a field can take a quite a bit of detective work. This tutorial takes you through that process. **At least have a read even if you don't actually do the steps yourself.** -
    + -* Table of Contents -{:toc} + + ## Safely deleting `Address` @@ -50,10 +53,10 @@ Let’s try removing references to `Address` in `EditPersonDescriptor`. 1. Remove the usages of `address` and select `Do refactor` when you are done. -
    + - :bulb: **Tip:** Removing usages may result in errors. Exercise discretion and fix them. For example, removing the `address` field from the `Person` class will require you to modify its constructor. -
    + **Tip:** Removing usages may result in errors. Exercise discretion and fix them. For example, removing the `address` field from the `Person` class will require you to modify its constructor. + 1. Repeat the steps for the remaining usages of `Address` @@ -71,7 +74,7 @@ A quick look at the `PersonCard` class and its `fxml` file quickly reveals why i **`PersonCard.java`** -``` java +```java ... @FXML private Label address; diff --git a/docs/tutorials/TracingCode.md b/docs/tutorials/TracingCode.md index 4fb62a83ef6..2b1b0f2d6b7 100644 --- a/docs/tutorials/TracingCode.md +++ b/docs/tutorials/TracingCode.md @@ -1,26 +1,30 @@ --- -layout: page -title: "Tutorial: Tracing code" + layout: default.md + title: "Tutorial: Tracing code" + pageNav: 3 --- +# Tutorial: Tracing code + + > Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. …​\[Therefore,\] making it easy to read makes it easier to write. > > — Robert C. Martin Clean Code: A Handbook of Agile Software Craftsmanship When trying to understand an unfamiliar code base, one common strategy used is to trace some representative execution path through the code base. One easy way to trace an execution path is to use a debugger to step through the code. In this tutorial, you will be using the IntelliJ IDEA’s debugger to trace the execution path of a specific user command. -* Table of Contents -{:toc} + + ## Before we start Before we jump into the code, it is useful to get an idea of the overall structure and the high-level behavior of the application. This is provided in the 'Architecture' section of the developer guide. In particular, the architecture diagram (reproduced below), tells us that the App consists of several components. -![ArchitectureDiagram](../images/ArchitectureDiagram.png) + It also has a sequence diagram (reproduced below) that tells us how a command propagates through the App. - + Note how the diagram shows only the execution flows _between_ the main components. That is, it does not show details of the execution path *inside* each component. By hiding those details, the diagram aims to inform the reader about the overall execution path of a command without overwhelming the reader with too much details. In this tutorial, you aim to find those omitted details so that you get a more in-depth understanding of how the code works. @@ -37,16 +41,16 @@ As you know, the first step of debugging is to put in a breakpoint where you wan In our case, we would want to begin the tracing at the very point where the App start processing user input (i.e., somewhere in the UI component), and then trace through how the execution proceeds through the UI component. However, the execution path through a GUI is often somewhat obscure due to various *event-driven mechanisms* used by GUI frameworks, which happens to be the case here too. Therefore, let us put the breakpoint where the `UI` transfers control to the `Logic` component. - + According to the sequence diagram you saw earlier (and repeated above for reference), the `UI` component yields control to the `Logic` component through a method named `execute`. Searching through the code base for an `execute()` method that belongs to the `Logic` component yields a promising candidate in `seedu.address.logic.Logic`. -
    + -:bulb: **Intellij Tip:** The ['**Search Everywhere**' feature](https://www.jetbrains.com/help/idea/searching-everywhere.html) can be used here. In particular, the '**Find Symbol**' ('Symbol' here refers to methods, variables, classes etc.) variant of that feature is quite useful here as we are looking for a _method_ named `execute`, not simply the text `execute`. -
    +**Intellij Tip:** The ['**Search Everywhere**' feature](https://www.jetbrains.com/help/idea/searching-everywhere.html) can be used here. In particular, the '**Find Symbol**' ('Symbol' here refers to methods, variables, classes etc.) variant of that feature is quite useful here as we are looking for a _method_ named `execute`, not simply the text `execute`. + A quick look at the `seedu.address.logic.Logic` (an extract given below) confirms that this indeed might be what we’re looking for. @@ -67,14 +71,14 @@ public interface Logic { But apparently, this is an interface, not a concrete implementation. That should be fine because the [Architecture section of the Developer Guide](../DeveloperGuide.html#architecture) tells us that components interact through interfaces. Here's the relevant diagram: - + Next, let's find out which statement(s) in the `UI` code is calling this method, thus transferring control from the `UI` to the `Logic`. -
    + -:bulb: **Intellij Tip:** The ['**Find Usages**' feature](https://www.jetbrains.com/help/idea/find-highlight-usages.html#find-usages) can find from which parts of the code a class/method/variable is being used. -
    +**Intellij Tip:** The ['**Find Usages**' feature](https://www.jetbrains.com/help/idea/find-highlight-usages.html#find-usages) can find from which parts of the code a class/method/variable is being used. + ![`Find Usages` tool window. `Edit` \> `Find` \> `Find Usages`.](../images/tracing/FindUsages.png) @@ -87,10 +91,10 @@ Now let’s set the breakpoint. First, double-click the item to reach the corres Recall from the User Guide that the `edit` command has the format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` For this tutorial we will be issuing the command `edit 1 n/Alice Yeoh`. -
    + -:bulb: **Tip:** Over the course of the debugging session, you will encounter every major component in the application. Try to keep track of what happens inside the component and where the execution transfers to another component. -
    +**Tip:** Over the course of the debugging session, you will encounter every major component in the application. Try to keep track of what happens inside the component and where the execution transfers to another component. + 1. To start the debugging session, simply `Run` \> `Debug Main` @@ -110,7 +114,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ **LogicManager\#execute().** - ``` java + ```java @Override public CommandResult execute(String commandText) throws CommandException, ParseException { @@ -142,7 +146,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ ![StepOver](../images/tracing/StepOver.png) 1. _Step into_ the line where user input in parsed from a String to a Command, which should bring you to the `AddressBookParser#parseCommand()` method (partial code given below): - ``` java + ```java public Command parseCommand(String userInput) throws ParseException { ... final String commandWord = matcher.group("commandWord"); @@ -157,7 +161,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ 1. Stepping through the `switch` block, we end up at a call to `EditCommandParser().parse()` as expected (because the command we typed is an edit command). - ``` java + ```java ... case EditCommand.COMMAND_WORD: return new EditCommandParser().parse(arguments); @@ -166,8 +170,10 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ 1. Let’s see what `EditCommandParser#parse()` does by stepping into it. You might have to click the 'step into' button multiple times here because there are two method calls in that statement: `EditCommandParser()` and `parse()`. -
    :bulb: **Intellij Tip:** Sometimes, you might end up stepping into functions that are not of interest. Simply use the `step out` button to get out of them! -
    + + + **Intellij Tip:** Sometimes, you might end up stepping into functions that are not of interest. Simply use the `step out` button to get out of them! + 1. Stepping through the method shows that it calls `ArgumentTokenizer#tokenize()` and `ParserUtil#parseIndex()` to obtain the arguments and index required. @@ -175,17 +181,17 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ ![EditCommand](../images/tracing/EditCommand.png) 1. As you just traced through some code involved in parsing a command, you can take a look at this class diagram to see where the various parsing-related classes you encountered fit into the design of the `Logic` component. - + 1. Let’s continue stepping through until we return to `LogicManager#execute()`. The sequence diagram below shows the details of the execution path through the Logic component. Does the execution path you traced in the code so far match the diagram?
    - ![Tracing an `edit` command through the Logic component](../images/tracing/LogicSequenceDiagram.png) + 1. Now, step over until you read the statement that calls the `execute()` method of the `EditCommand` object received, and step into that `execute()` method (partial code given below): **`EditCommand#execute()`:** - ``` java + ```java @Override public CommandResult execute(Model model) throws CommandException { ... @@ -205,25 +211,28 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ * it uses the `updateFilteredPersonList` method to ask the `Model` to populate the 'filtered list' with _all_ persons.
    FYI, The 'filtered list' is the list of persons resulting from the most recent operation that will be shown to the user immediately after. For the `edit` command, we populate it with all the persons so that the user can see the edited person along with all other persons. If this was a `find` command, we would be setting that list to contain the search results instead.
    To provide some context, given below is the class diagram of the `Model` component. See if you can figure out where the 'filtered list' of persons is being tracked. -
    +
    * :bulb: This may be a good time to read through the [`Model` component section of the DG](../DeveloperGuide.html#model-component) 1. As you step through the rest of the statements in the `EditCommand#execute()` method, you'll see that it creates a `CommandResult` object (containing information about the result of the execution) and returns it.
    Advancing the debugger by one more step should take you back to the middle of the `LogicManager#execute()` method.
    1. Given that you have already seen quite a few classes in the `Logic` component in action, see if you can identify in this partial class diagram some of the classes you've encountered so far, and see how they fit into the class structure of the `Logic` component: - + + * :bulb: This may be a good time to read through the [`Logic` component section of the DG](../DeveloperGuide.html#logic-component) 1. Similar to before, you can step over/into statements in the `LogicManager#execute()` method to examine how the control is transferred to the `Storage` component and what happens inside that component. -
    :bulb: **Intellij Tip:** When trying to step into a statement such as `storage.saveAddressBook(model.getAddressBook())` which contains multiple method calls, Intellij will let you choose (by clicking) which one you want to step into. -
    + + + **Intellij Tip:** When trying to step into a statement such as `storage.saveAddressBook(model.getAddressBook())` which contains multiple method calls, Intellij will let you choose (by clicking) which one you want to step into. + -1. As you step through the code inside the `Storage` component, you will eventually arrive at the `JsonAddressBook#saveAddressBook()` method which calls the `JsonSerializableAddressBook` constructor, to create an object that can be _serialized_ (i.e., stored in storage medium) in JSON format. That constructor is given below (with added line breaks for easier readability): +1. As you step through the code inside the `Storage` component, you will eventually arrive at the `JsonAddressBook#saveAddressBook()` method which calls the `JsonSerializableAddressBook` constructor, to create an object that can be _serialized_ (i.e., stored in storage medium) in JSON format. That constructor is given below (with added line breaks for easier readability): **`JsonSerializableAddressBook` constructor:** - ``` java + ```java /** * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use. * @@ -243,7 +252,8 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ This is because regular Java objects need to go through an _adaptation_ for them to be suitable to be saved in JSON format. 1. While you are stepping through the classes in the `Storage` component, here is the component's class diagram to help you understand how those classes fit into the structure of the component.
    - + + * :bulb: This may be a good time to read through the [`Storage` component section of the DG](../DeveloperGuide.html#storage-component) 1. We can continue to step through until you reach the end of the `LogicManager#execute()` method and return to the `MainWindow#executeCommand()` method (the place where we put the original breakpoint). @@ -251,7 +261,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ 1. Stepping into `resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser());`, we end up in: **`ResultDisplay#setFeedbackToUser()`** - ``` java + ```java public void setFeedbackToUser(String feedbackToUser) { requireNonNull(feedbackToUser); resultDisplay.setText(feedbackToUser); diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 3d6bd06d5af..05452ecae7e 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -48,7 +48,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing MedBook ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -68,9 +68,12 @@ public void init() throws Exception { } /** - * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
    - * The data from the sample address book will be used instead if {@code storage}'s address book is not found, - * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. + * Returns a {@code ModelManager} with the data from {@code storage}'s address + * book and {@code userPrefs}.
    + * The data from the sample address book will be used instead if + * {@code storage}'s address book is not found, + * or an empty address book will be used instead if errors occur when reading + * {@code storage}'s address book. */ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { logger.info("Using data file : " + storage.getAddressBookFilePath()); @@ -127,7 +130,8 @@ protected Config initConfig(Path configFilePath) { initializedConfig = new Config(); } - //Update config file in case it was missing to begin with or there are new/unused fields + // Update config file in case it was missing to begin with or there are + // new/unused fields try { ConfigUtil.saveConfig(initializedConfig, configFilePathUsed); } catch (IOException e) { @@ -137,7 +141,8 @@ protected Config initConfig(Path configFilePath) { } /** - * Returns a {@code UserPrefs} using the file at {@code storage}'s user prefs file path, + * Returns a {@code UserPrefs} using the file at {@code storage}'s user prefs + * file path, * or a new {@code UserPrefs} with default configuration if errors occur when * reading from the file. */ @@ -158,7 +163,8 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { initializedPrefs = new UserPrefs(); } - //Update prefs file in case it was missing to begin with or there are new/unused fields + // Update prefs file in case it was missing to begin with or there are + // new/unused fields try { storage.saveUserPrefs(initializedPrefs); } catch (IOException e) { @@ -170,7 +176,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting MedBook " + MainApp.VERSION); ui.start(primaryStage); } diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/address/commons/core/GuiSettings.java index a97a86ee8d7..5dced64a674 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/seedu/address/commons/core/GuiSettings.java @@ -12,8 +12,8 @@ */ public class GuiSettings implements Serializable { - private static final double DEFAULT_HEIGHT = 600; - private static final double DEFAULT_WIDTH = 740; + private static final double DEFAULT_HEIGHT = 720; + private static final double DEFAULT_WIDTH = 1280; private final double windowWidth; private final double windowHeight; diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/address/commons/core/LogsCenter.java index 8cf8e15a0f0..86305e85430 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/address/commons/core/LogsCenter.java @@ -20,7 +20,7 @@ public class LogsCenter { private static final int MAX_FILE_COUNT = 5; private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB - private static final String LOG_FILE = "addressbook.log"; + private static final String LOG_FILE = "medbook.log"; private static final Logger logger; // logger for this class private static Logger baseLogger; // to be used as the parent of all other loggers created by this class. private static Level currentLogLevel = Level.INFO; diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..0424928ab9c 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -8,7 +8,9 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.appointment.Appointment; import seedu.address.model.person.Person; +import seedu.address.model.record.Record; /** * API of the Logic component @@ -16,10 +18,11 @@ public interface Logic { /** * Executes the command and returns the result. + * * @param commandText The command as entered by the user. * @return the result of the command execution. * @throws CommandException If an error occurs during command execution. - * @throws ParseException If an error occurs during parsing. + * @throws ParseException If an error occurs during parsing. */ CommandResult execute(String commandText) throws CommandException, ParseException; @@ -33,6 +36,15 @@ public interface Logic { /** Returns an unmodifiable view of the filtered list of persons */ ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the pinned list of persons */ + ObservableList getPinnedPersonList(); + + /** Returns an unmodifiable view of the filtered list of appointments */ + ObservableList getFilteredAppointmentList(); + + /** Returns an unmodifiable view of the filtered list of records */ + ObservableList getFilteredRecordList(); + /** * Returns the user prefs' address book file path. */ @@ -47,4 +59,9 @@ public interface Logic { * Set the user prefs' GUI settings. */ void setGuiSettings(GuiSettings guiSettings); + + ObservableList getRecordList(); + + ObservableList getPersonBeingViewed(); + } diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 5aa3b91c7d0..c7489eee3b8 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -15,7 +15,9 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.appointment.Appointment; import seedu.address.model.person.Person; +import seedu.address.model.record.Record; import seedu.address.storage.Storage; /** @@ -24,8 +26,8 @@ public class LogicManager implements Logic { public static final String FILE_OPS_ERROR_FORMAT = "Could not save data due to the following error: %s"; - public static final String FILE_OPS_PERMISSION_ERROR_FORMAT = - "Could not save data to file %s due to insufficient permissions to write to the file or the folder."; + public static final String FILE_OPS_PERMISSION_ERROR_FORMAT = "Could not save data to file %s due to " + + "insufficient permissions to write to the file or the folder."; private final Logger logger = LogsCenter.getLogger(LogicManager.class); @@ -34,7 +36,8 @@ public class LogicManager implements Logic { private final AddressBookParser addressBookParser; /** - * Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}. + * Constructs a {@code LogicManager} with the given {@code Model} and + * {@code Storage}. */ public LogicManager(Model model, Storage storage) { this.model = model; @@ -71,6 +74,30 @@ public ObservableList getFilteredPersonList() { return model.getFilteredPersonList(); } + @Override + public ObservableList getPinnedPersonList() { + return model.getPinnedPersonList(); + } + + @Override + public ObservableList getFilteredAppointmentList() { + return model.getAppointmentList(); + } + + @Override + public ObservableList getRecordList() { + return model.getRecordList(); + } + + public ObservableList getFilteredRecordList() { + return model.getFilteredRecordList(); + } + + @Override + public ObservableList getPersonBeingViewed() { + return model.getPersonBeingViewed(); + } + @Override public Path getAddressBookFilePath() { return model.getAddressBookFilePath(); diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java index ecd32c31b53..07dfcc19d41 100644 --- a/src/main/java/seedu/address/logic/Messages.java +++ b/src/main/java/seedu/address/logic/Messages.java @@ -5,7 +5,9 @@ import java.util.stream.Stream; import seedu.address.logic.parser.Prefix; +import seedu.address.model.appointment.Appointment; import seedu.address.model.person.Person; +import seedu.address.model.record.Record; /** * Container for user visible messages. @@ -15,9 +17,13 @@ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; + public static final String MESSAGE_INVALID_APPOINTMENT_DISPLAYED_INDEX = + "The appointment index provided is invalid"; + public static final String MESSAGE_INVALID_RECORD_DISPLAYED_INDEX = "The record index provided is invalid"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_RECORDS_LISTED_OVERVIEW = "%1$d records listed!"; public static final String MESSAGE_DUPLICATE_FIELDS = - "Multiple values specified for the following single-valued field(s): "; + "Multiple values specified for the following single-valued field(s): "; /** * Returns an error message indicating the duplicate prefixes. @@ -25,8 +31,7 @@ public class Messages { public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePrefixes) { assert duplicatePrefixes.length > 0; - Set duplicateFields = - Stream.of(duplicatePrefixes).map(Prefix::toString).collect(Collectors.toSet()); + Set duplicateFields = Stream.of(duplicatePrefixes).map(Prefix::toString).collect(Collectors.toSet()); return MESSAGE_DUPLICATE_FIELDS + String.join(" ", duplicateFields); } @@ -37,15 +42,51 @@ public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePref public static String format(Person person) { final StringBuilder builder = new StringBuilder(); builder.append(person.getName()) - .append("; Phone: ") - .append(person.getPhone()) + .append("; NRIC: ") + .append(person.getNric()) .append("; Email: ") .append(person.getEmail()) - .append("; Address: ") - .append(person.getAddress()) - .append("; Tags: "); - person.getTags().forEach(builder::append); + .append("; Phone: ") + .append(person.getPhone()) + .append("; Gender: ") + .append(person.getGender()) + .append("; Age: ") + .append(person.getAge()) + .append("; BloodType: ") + .append(person.getBloodType()) + .append("; Allergies: "); + person.getAllergies().forEach(builder::append); + return builder.toString(); + } + + /** + * Formats the {@code appointment} for display to the user. + */ + public static String format(Appointment appointment, Person person) { + final StringBuilder builder = new StringBuilder(); + builder.append("Patient: ") + .append(person.getName()) + .append("; Appointment: ") + .append(appointment.getName()) + .append("; Date & Time: ") + .append(appointment.getDateTime()); + return builder.toString(); } + /** + * Formats the {@code record} for display to the user. + */ + public static String format(Record record, Person person) { + final StringBuilder builder = new StringBuilder(); + builder.append("Patient: ") + .append(person.getName()) + .append("; Conditions: ") + .append(record.getConditions()) + .append("; Date & Time: ") + .append(record.getDateTime()) + .append("; Medications: ") + .append(record.getMedications()); + return builder.toString(); + } } diff --git a/src/main/java/seedu/address/logic/commands/AddAppointmentCommand.java b/src/main/java/seedu/address/logic/commands/AddAppointmentCommand.java new file mode 100644 index 00000000000..c07becc1b21 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddAppointmentCommand.java @@ -0,0 +1,101 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.appointment.Appointment; +import seedu.address.model.appointment.UniqueAppointmentList; +import seedu.address.model.person.Person; +import seedu.address.model.shared.Nric; + +/** + * Adds an appointment to the address book. + */ +public class AddAppointmentCommand extends Command { + + public static final String COMMAND_WORD = "addappointment"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds an appointment to the address book.\n" + + "Parameters: INDEX (must be a positive integer) " + + PREFIX_NAME + "NAME " + + PREFIX_DATE + "DATETIME\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_NAME + "Eye Examination " + + PREFIX_DATE + "18-09-2023 1800 "; + + public static final String MESSAGE_SUCCESS = "New appointment added: %1$s"; + public static final String MESSAGE_DUPLICATE_APPOINTMENT = "This appointment already exists in the address book"; + + private final Index index; + private final Appointment toAdd; + + /** + * Creates an AddAppointmentCommand to add the specified {@code Appointment} + */ + public AddAppointmentCommand(Index index, Appointment appointment) { + requireNonNull(index); + requireNonNull(appointment); + this.toAdd = appointment; + this.index = index; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person patient = lastShownList.get(index.getZeroBased()); + Nric patientId = patient.getNric(); + Appointment newAppointment = new Appointment(toAdd.getName(), toAdd.getDateTime(), patientId); + if (patient.hasAppointment(newAppointment)) { + throw new CommandException(MESSAGE_DUPLICATE_APPOINTMENT); + } + + UniqueAppointmentList newAppointmentList = new UniqueAppointmentList(); + newAppointmentList.setAppointments(patient.getAppointments()); + newAppointmentList.add(newAppointment); + + Person newPatient = new Person(patient.getName(), patient.getNric(), patient.getEmail(), + patient.getPhone(), patient.getGender(), patient.getAge(), patient.getBloodType(), + patient.getAllergies(), patient.getRecords(), newAppointmentList, patient.isPinned()); + + model.setPerson(patient, newPatient); + + model.resetAppointmentList(); + return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(newAppointment, newPatient))); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddAppointmentCommand)) { + return false; + } + + AddAppointmentCommand otherAddCommand = (AddAppointmentCommand) other; + return toAdd.equals(otherAddCommand.toAdd) && index.equals(otherAddCommand.index); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("toAdd", toAdd) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index 5d7185a9680..4f6a617d15c 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -1,11 +1,14 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_AGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ALLERGIES; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BLOODTYPE; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NRIC; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import seedu.address.commons.util.ToStringBuilder; import seedu.address.logic.Messages; @@ -18,25 +21,31 @@ */ public class AddCommand extends Command { - public static final String COMMAND_WORD = "add"; + public static final String COMMAND_WORD = "addpatient"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a patient to the address book.\n" + "Parameters: " + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " + + PREFIX_NRIC + "NRIC " + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" + + PREFIX_PHONE + "PHONE " + + PREFIX_GENDER + "GENDER " + + PREFIX_AGE + "AGE " + + PREFIX_BLOODTYPE + "BLOODTYPE " + + "[" + PREFIX_ALLERGIES + "ALLERGY]...\n" + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + "John Doe " - + PREFIX_PHONE + "98765432 " + + PREFIX_NRIC + "S1234567A " + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; + + PREFIX_PHONE + "98765432 " + + PREFIX_GENDER + "M " + + PREFIX_AGE + "18 " + + PREFIX_BLOODTYPE + "A+ " + + PREFIX_ALLERGIES + "Penicillin "; + public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; + public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the Medbook"; private final Person toAdd; diff --git a/src/main/java/seedu/address/logic/commands/AddRecordCommand.java b/src/main/java/seedu/address/logic/commands/AddRecordCommand.java new file mode 100644 index 00000000000..21d7290b663 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddRecordCommand.java @@ -0,0 +1,104 @@ +package seedu.address.logic.commands; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONDITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MEDICATION; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; +import seedu.address.model.record.Record; +import seedu.address.model.record.UniqueRecordList; + +/** + * Adds a record to a patient in the MedBook + */ +public class AddRecordCommand extends Command { + + public static final String COMMAND_WORD = "addrecord"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a patient record to the address book.\n" + + "Parameters: INDEX (must be a positive integer) " + + PREFIX_DATE + "DATETIME " + + PREFIX_CONDITION + "CONDITION " + + PREFIX_MEDICATION + "MEDICATION " + "\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_DATE + "18-09-2023 1800 " + + PREFIX_CONDITION + "Fever " + + PREFIX_MEDICATION + "Tylenol"; + + public static final String MESSAGE_SUCCESS = "New record added: %1$s"; + + public static final String MESSAGE_DUPLICATE_RECORDS = "This record already exists in the record of the patient."; + + private final Record record; + + private final Index index; + + /** + * Creates a record under a patient of specified index + */ + public AddRecordCommand(Index index, Record record) { + requireAllNonNull(index, record); + this.index = index; + this.record = record; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + + List lastShownList = model.getFilteredPersonList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + if (model.hasRecord(record, index)) { + throw new CommandException(MESSAGE_DUPLICATE_RECORDS); + } + + Person personToAddRecord = lastShownList.get(index.getZeroBased()); + UniqueRecordList newRecords = new UniqueRecordList(); + newRecords.setRecords(personToAddRecord.getRecords()); + newRecords.add(record); + Person personWithAddedRecord = new Person(personToAddRecord.getName(), personToAddRecord.getNric(), + personToAddRecord.getEmail(), personToAddRecord.getPhone(), personToAddRecord.getGender(), + personToAddRecord.getAge(), personToAddRecord.getBloodType(), personToAddRecord.getAllergies(), + newRecords, personToAddRecord.getAppointments(), personToAddRecord.isPinned()); + + model.setPerson(personToAddRecord, personWithAddedRecord); + model.updateRecordList(personWithAddedRecord); + + return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(record, personWithAddedRecord))); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddRecordCommand)) { + return false; + } + + AddRecordCommand otherAddRecordCommand = (AddRecordCommand) other; + return record.equals(otherAddRecordCommand.record) + && index.equals(otherAddRecordCommand.index); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("index", index) + .add("record", record) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java deleted file mode 100644 index 9c86b1fa6e4..00000000000 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ /dev/null @@ -1,23 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.model.AddressBook; -import seedu.address.model.Model; - -/** - * Clears the address book. - */ -public class ClearCommand extends Command { - - public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.setAddressBook(new AddressBook()); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java index 249b6072d0d..0276875de7b 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -16,15 +16,19 @@ public class CommandResult { /** Help information should be shown to the user. */ private final boolean showHelp; + /** Appointments should be shown to the user. */ + private final boolean showAppointments; + /** The application should exit. */ private final boolean exit; /** * Constructs a {@code CommandResult} with the specified fields. */ - public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { + public CommandResult(String feedbackToUser, boolean showHelp, boolean showAppointments, boolean exit) { this.feedbackToUser = requireNonNull(feedbackToUser); this.showHelp = showHelp; + this.showAppointments = showAppointments; this.exit = exit; } @@ -33,7 +37,7 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { * and other fields set to their default value. */ public CommandResult(String feedbackToUser) { - this(feedbackToUser, false, false); + this(feedbackToUser, false, false, false); } public String getFeedbackToUser() { @@ -44,6 +48,10 @@ public boolean isShowHelp() { return showHelp; } + public boolean isShowAppointments() { + return showAppointments; + } + public boolean isExit() { return exit; } @@ -62,12 +70,13 @@ public boolean equals(Object other) { CommandResult otherCommandResult = (CommandResult) other; return feedbackToUser.equals(otherCommandResult.feedbackToUser) && showHelp == otherCommandResult.showHelp + && showAppointments == otherCommandResult.showAppointments && exit == otherCommandResult.exit; } @Override public int hashCode() { - return Objects.hash(feedbackToUser, showHelp, exit); + return Objects.hash(feedbackToUser, showHelp, showAppointments, exit); } @Override @@ -75,6 +84,7 @@ public String toString() { return new ToStringBuilder(this) .add("feedbackToUser", feedbackToUser) .add("showHelp", showHelp) + .add("showAppointments", showAppointments) .add("exit", exit) .toString(); } diff --git a/src/main/java/seedu/address/logic/commands/DeleteAppointmentCommand.java b/src/main/java/seedu/address/logic/commands/DeleteAppointmentCommand.java new file mode 100644 index 00000000000..94398cafb4b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteAppointmentCommand.java @@ -0,0 +1,97 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.appointment.Appointment; +import seedu.address.model.appointment.UniqueAppointmentList; +import seedu.address.model.person.Person; +import seedu.address.model.shared.Nric; + +/** + * Deletes an appointment identified using it's displayed index from the address + * book. + */ +public class DeleteAppointmentCommand extends Command { + + public static final String COMMAND_WORD = "deleteappointment"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the appointment identified by the index number used in the displayed appointment list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_APPOINTMENT_SUCCESS = "Deleted Appointment: %1$s"; + + public static final String MESSAGE_INVALID_NRIC = "The Patient with the corresponding NRIC no longer exists"; + + private final Index targetIndex; + + public DeleteAppointmentCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List appointmentList = model.getAppointmentList(); + List personList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= appointmentList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_APPOINTMENT_DISPLAYED_INDEX); + } + + Appointment appointmentToDelete = appointmentList.get(targetIndex.getZeroBased()); + Nric patientNric = appointmentToDelete.getNric(); + Person personWithAppointment = personList.stream() + .filter(person -> person.getNric().equals(patientNric)) + .findFirst() + .orElse(null); + if (personWithAppointment == null) { + throw new CommandException(MESSAGE_INVALID_NRIC); + } + UniqueAppointmentList newList = new UniqueAppointmentList(); + newList.setAppointments(personWithAppointment.getAppointments()); + newList.remove(appointmentToDelete); + + Person newPatient = new Person(personWithAppointment.getName(), personWithAppointment.getNric(), + personWithAppointment.getEmail(), + personWithAppointment.getPhone(), personWithAppointment.getGender(), personWithAppointment.getAge(), + personWithAppointment.getBloodType(), + personWithAppointment.getAllergies(), personWithAppointment.getRecords(), newList, + personWithAppointment.isPinned()); + + model.setPerson(personWithAppointment, newPatient); + model.resetAppointmentList(); + return new CommandResult(String.format(MESSAGE_DELETE_APPOINTMENT_SUCCESS, + Messages.format(appointmentToDelete, personWithAppointment))); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeleteAppointmentCommand)) { + return false; + } + + DeleteAppointmentCommand otherDeleteAppointmentCommand = (DeleteAppointmentCommand) other; + return targetIndex.equals(otherDeleteAppointmentCommand.targetIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndex) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index 1135ac19b74..54e84f9fe6d 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -42,6 +42,7 @@ public CommandResult execute(Model model) throws CommandException { Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); model.deletePerson(personToDelete); + model.resetAppointmentList(); return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete))); } diff --git a/src/main/java/seedu/address/logic/commands/DeleteRecordCommand.java b/src/main/java/seedu/address/logic/commands/DeleteRecordCommand.java new file mode 100644 index 00000000000..3ff1580b19a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteRecordCommand.java @@ -0,0 +1,88 @@ +package seedu.address.logic.commands; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; +import seedu.address.model.record.Record; +import seedu.address.model.record.UniqueRecordList; + +/** + * Deletes a record identified using it's displayed patient index and record + * index from the address book. + */ +public class DeleteRecordCommand extends Command { + public static final String COMMAND_WORD = "deleterecord"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the record identified by the index number used " + + "in the displayed patient list and record list.\n" + + "Parameters: PATIENT INDEX/RECORD INDEX (Both must be positive integers)\n" + + "Example: " + COMMAND_WORD + " 1/1"; + + public static final String MESSAGE_DELETE_RECORD_SUCCESS = "Deleted Record: %1$s"; + + private final Index targetPatientIndex; + private final Index targetRecordIndex; + + /** + * Creates DeleteRecordCommand object + */ + public DeleteRecordCommand(Index targetPatientIndex, Index targetRecordIndex) { + this.targetPatientIndex = targetPatientIndex; + this.targetRecordIndex = targetRecordIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (targetPatientIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + Person targetPatient = lastShownList.get(targetPatientIndex.getZeroBased()); + UniqueRecordList newRecordsList = new UniqueRecordList(); + newRecordsList.setRecords(targetPatient.getRecords()); + + if (targetRecordIndex.getZeroBased() >= newRecordsList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_RECORD_DISPLAYED_INDEX); + } + + Record targetRecord = newRecordsList.get(targetRecordIndex.getZeroBased()); + newRecordsList.remove(targetRecord); + Person patientWithDeletedRecord = new Person(targetPatient.getName(), targetPatient.getNric(), + targetPatient.getEmail(), targetPatient.getPhone(), targetPatient.getGender(), targetPatient.getAge(), + targetPatient.getBloodType(), targetPatient.getAllergies(), newRecordsList, + targetPatient.getAppointments(), targetPatient.isPinned()); + model.setPerson(targetPatient, patientWithDeletedRecord); + model.updateRecordList(patientWithDeletedRecord); + return new CommandResult(String.format(MESSAGE_DELETE_RECORD_SUCCESS, + Messages.format(targetRecord, targetPatient))); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (!(other instanceof DeleteRecordCommand)) { + return false; + } + + DeleteRecordCommand otherDeleteRecordCommand = (DeleteRecordCommand) other; + return this.targetPatientIndex.equals(otherDeleteRecordCommand.targetPatientIndex) + && this.targetRecordIndex.equals(otherDeleteRecordCommand.targetRecordIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetPatientIndex", targetPatientIndex) + .add("targetRecordIndex", targetRecordIndex) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index 4b581c7331e..ab8e47db92c 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -1,11 +1,13 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_AGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ALLERGIES; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BLOODTYPE; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; import java.util.Collections; @@ -21,29 +23,36 @@ import seedu.address.logic.Messages; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.Address; +import seedu.address.model.appointment.UniqueAppointmentList; +import seedu.address.model.person.Age; +import seedu.address.model.person.Allergy; +import seedu.address.model.person.BloodType; import seedu.address.model.person.Email; -import seedu.address.model.person.Name; +import seedu.address.model.person.Gender; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.record.UniqueRecordList; +import seedu.address.model.shared.Name; +import seedu.address.model.shared.Nric; /** * Edits the details of an existing person in the address book. */ public class EditCommand extends Command { - public static final String COMMAND_WORD = "edit"; + public static final String COMMAND_WORD = "editpatient"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " + "by the index number used in the displayed person list. " + "Existing values will be overwritten by the input values.\n" + "Parameters: INDEX (must be a positive integer) " + "[" + PREFIX_NAME + "NAME] " - + "[" + PREFIX_PHONE + "PHONE] " + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" + + "[" + PREFIX_PHONE + "PHONE] " + + "[" + PREFIX_GENDER + "GENDER] " + + "[" + PREFIX_AGE + "AGE] " + + "[" + PREFIX_BLOODTYPE + "BLOODTYPE " + + "[" + PREFIX_ALLERGIES + "ALLERGY]...\n" + "Example: " + COMMAND_WORD + " 1 " + PREFIX_PHONE + "91234567 " + PREFIX_EMAIL + "johndoe@example.com"; @@ -56,8 +65,10 @@ public class EditCommand extends Command { private final EditPersonDescriptor editPersonDescriptor; /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with + * @param index + * of the person in the filtered person list to edit + * @param editPersonDescriptor + * details to edit the person with */ public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { requireNonNull(index); @@ -85,7 +96,9 @@ public CommandResult execute(Model model) throws CommandException { model.setPerson(personToEdit, editedPerson); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson))); + model.updateRecordList(editedPerson); + return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, + Messages.format(editedPerson))); } /** @@ -96,12 +109,19 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript assert personToEdit != null; Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); + Nric updatedNric = editPersonDescriptor.getNric().orElse(personToEdit.getNric()); Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); + Gender updatedGender = editPersonDescriptor.getGender().orElse(personToEdit.getGender()); + Age updatedAge = editPersonDescriptor.getAge().orElse(personToEdit.getAge()); + BloodType updatedBloodType = editPersonDescriptor.getBloodType().orElse(personToEdit.getBloodType()); + Set updatedAllergies = editPersonDescriptor.getAllergies().orElse(personToEdit.getAllergies()); + UniqueRecordList updatedRecords = personToEdit.getRecords(); + UniqueAppointmentList updatedAppointments = personToEdit.getAppointments(); + Boolean updatedisPinned = personToEdit.isPinned(); + + return new Person(updatedName, updatedNric, updatedEmail, updatedPhone, updatedGender, + updatedAge, updatedBloodType, updatedAllergies, updatedRecords, updatedAppointments, updatedisPinned); } @Override @@ -129,84 +149,117 @@ public String toString() { } /** - * Stores the details to edit the person with. Each non-empty field value will replace the + * Stores the details to edit the person with. Each non-empty field value will + * replace the * corresponding field value of the person. */ public static class EditPersonDescriptor { private Name name; - private Phone phone; + private Nric nric; private Email email; - private Address address; - private Set tags; + private Phone phone; + private Gender gender; + private Age age; + private BloodType bloodType; + private Set allergies; - public EditPersonDescriptor() {} + public EditPersonDescriptor() { + } /** * Copy constructor. - * A defensive copy of {@code tags} is used internally. + * A defensive copy of {@code allergies} is used internally. */ public EditPersonDescriptor(EditPersonDescriptor toCopy) { setName(toCopy.name); - setPhone(toCopy.phone); + setNric(toCopy.nric); setEmail(toCopy.email); - setAddress(toCopy.address); - setTags(toCopy.tags); + setPhone(toCopy.phone); + setGender(toCopy.gender); + setAge(toCopy.age); + setBloodType(toCopy.bloodType); + setAllergies(toCopy.allergies); } /** * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); + return CollectionUtil.isAnyNonNull(name, email, phone, gender, age, bloodType, allergies); + } + + public Optional getName() { + return Optional.ofNullable(name); } public void setName(Name name) { this.name = name; } - public Optional getName() { - return Optional.ofNullable(name); + public Optional getNric() { + return Optional.ofNullable(nric); } - public void setPhone(Phone phone) { - this.phone = phone; + public void setNric(Nric nric) { + this.nric = nric; } public Optional getPhone() { return Optional.ofNullable(phone); } - public void setEmail(Email email) { - this.email = email; + public void setPhone(Phone phone) { + this.phone = phone; } public Optional getEmail() { return Optional.ofNullable(email); } - public void setAddress(Address address) { - this.address = address; + public void setEmail(Email email) { + this.email = email; + } + + public Optional getGender() { + return Optional.ofNullable(gender); + } + + public void setGender(Gender gender) { + this.gender = gender; + } + + public Optional getAge() { + return Optional.ofNullable(age); } - public Optional
    getAddress() { - return Optional.ofNullable(address); + public void setAge(Age age) { + this.age = age; + } + + public Optional getBloodType() { + return Optional.ofNullable(bloodType); + } + + public void setBloodType(BloodType bloodType) { + this.bloodType = bloodType; } /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. + * Returns an unmodifiable Allergy set, which throws + * {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code allergies} is null. */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; + public Optional> getAllergies() { + return (allergies != null) ? Optional.of(Collections.unmodifiableSet(allergies)) : Optional.empty(); } /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. + * Sets {@code allergies} to this object's {@code tags}. + * A defensive copy of {@code allergies} is used internally. */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + public void setAllergies(Set allergies) { + this.allergies = (allergies != null) ? new HashSet<>(allergies) : null; } @Override @@ -222,20 +275,26 @@ public boolean equals(Object other) { EditPersonDescriptor otherEditPersonDescriptor = (EditPersonDescriptor) other; return Objects.equals(name, otherEditPersonDescriptor.name) - && Objects.equals(phone, otherEditPersonDescriptor.phone) + && Objects.equals(nric, otherEditPersonDescriptor.nric) && Objects.equals(email, otherEditPersonDescriptor.email) - && Objects.equals(address, otherEditPersonDescriptor.address) - && Objects.equals(tags, otherEditPersonDescriptor.tags); + && Objects.equals(phone, otherEditPersonDescriptor.phone) + && Objects.equals(gender, otherEditPersonDescriptor.gender) + && Objects.equals(age, otherEditPersonDescriptor.age) + && Objects.equals(bloodType, otherEditPersonDescriptor.bloodType) + && Objects.equals(allergies, otherEditPersonDescriptor.allergies); } @Override public String toString() { return new ToStringBuilder(this) .add("name", name) - .add("phone", phone) + .add("nric", nric) .add("email", email) - .add("address", address) - .add("tags", tags) + .add("phone", phone) + .add("gender", gender) + .add("age", age) + .add("bloodType", bloodType) + .add("allergies", allergies) .toString(); } } diff --git a/src/main/java/seedu/address/logic/commands/EditRecordCommand.java b/src/main/java/seedu/address/logic/commands/EditRecordCommand.java new file mode 100644 index 00000000000..cadd25d52b4 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/EditRecordCommand.java @@ -0,0 +1,292 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONDITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MEDICATION; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; +import seedu.address.model.record.Condition; +import seedu.address.model.record.Medication; +import seedu.address.model.record.Record; +import seedu.address.model.record.UniqueRecordList; +import seedu.address.model.shared.DateTime; + +/** + * Edits the details of an existing record of a Person in the address book. + */ +public class EditRecordCommand extends Command { + + public static final String COMMAND_WORD = "editrecord"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits a patient's record in the address book " + + "by the patient's index number and the record's index number.\n" + + "Existing values will be overwritten by the input values.\n" + + "Parameters: PATIENT'S INDEX/RECORD INDEX (both must be a positive integer) " + + "[" + PREFIX_DATE + "DATE] " + + "[" + PREFIX_CONDITION + "CONDITION] " + "\n" + + "Example: " + COMMAND_WORD + " 1/2 " + + PREFIX_DATE + "21092023 1800 " + + PREFIX_CONDITION + "Cold" + + PREFIX_MEDICATION + "Ibuprofen"; + + public static final String MESSAGE_EDIT_RECORD_SUCCESS = "Edited record: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_RECORD = "This record already exists in the record of the patient."; + + private final Index patientIndex; + private final Index recordIndex; + private final EditRecordCommand.EditRecordDescriptor editRecordDescriptor; + + /** + * @param patientIndex index of the person in the filtered person list + * to edit + * @param recordIndex index of the record of the targeted patient + * @param editRecordDescriptor details to edit the record with + */ + public EditRecordCommand(Index patientIndex, Index recordIndex, + EditRecordCommand.EditRecordDescriptor editRecordDescriptor) { + requireNonNull(patientIndex); + requireNonNull(recordIndex); + requireNonNull(editRecordDescriptor); + + this.patientIndex = patientIndex; + this.recordIndex = recordIndex; + this.editRecordDescriptor = new EditRecordCommand.EditRecordDescriptor(editRecordDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownPersonList = model.getFilteredPersonList(); + + if (patientIndex.getZeroBased() >= lastShownPersonList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person personToEdit = lastShownPersonList.get(patientIndex.getZeroBased()); + + UniqueRecordList uniqueRecordList = personToEdit.getRecords(); + List lastShownRecordList = uniqueRecordList.asUnmodifiableObservableList(); + + if (recordIndex.getZeroBased() >= lastShownRecordList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_RECORD_DISPLAYED_INDEX); + } + + Record recordToEdit = lastShownRecordList.get(recordIndex.getZeroBased()); + Record editedRecord = createEditedRecord(recordToEdit, editRecordDescriptor); + + if (recordToEdit.equals(editedRecord) || uniqueRecordList.contains(editedRecord)) { + throw new CommandException(MESSAGE_DUPLICATE_RECORD); + } + + UniqueRecordList newList = new UniqueRecordList(); + newList.setRecords(uniqueRecordList); + newList.setRecord(recordToEdit, editedRecord); + Person editedPerson = createdEditedPerson(personToEdit, newList); + + if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { + throw new CommandException(EditCommand.MESSAGE_DUPLICATE_PERSON); + } + + model.setPerson(personToEdit, editedPerson); + model.updateRecordList(editedPerson); + return new CommandResult(String.format(MESSAGE_EDIT_RECORD_SUCCESS, + Messages.format(editedRecord, personToEdit))); + } + + /** + * Creates and returns a {@code Record} with the details of {@code recordToEdit} + * edited with {@code editRecordDescriptor}. + */ + private Record createEditedRecord(Record recordToEdit, + EditRecordCommand.EditRecordDescriptor editRecordDescriptor) { + assert recordToEdit != null; + + DateTime updatedDateTime = editRecordDescriptor.getDateTime().orElse(recordToEdit.getDateTime()); + List updatedConditions = editRecordDescriptor.getConditions().orElse(recordToEdit.getConditions()); + Path filePath = editRecordDescriptor.getFilePath().orElse(recordToEdit.getFilePath()); + List updatedMedications = editRecordDescriptor.getMedications() + .orElse(recordToEdit.getMedications()); + + return new Record(updatedDateTime, updatedConditions, + updatedMedications, filePath, patientIndex.getZeroBased()); + } + + private static Person createdEditedPerson(Person personToEdit, UniqueRecordList records) { + assert personToEdit != null; + Person editedPerson = new Person(personToEdit.getName(), personToEdit.getNric(), personToEdit.getEmail(), + personToEdit.getPhone(), personToEdit.getGender(), + personToEdit.getAge(), personToEdit.getBloodType(), + personToEdit.getAllergies(), records, personToEdit.getAppointments(), + personToEdit.isPinned()); + + return editedPerson; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditRecordCommand)) { + return false; + } + + EditRecordCommand otherEditRecordCommand = (EditRecordCommand) other; + return patientIndex.equals(otherEditRecordCommand.patientIndex) + && recordIndex.equals(otherEditRecordCommand.recordIndex) + && editRecordDescriptor.equals(otherEditRecordCommand.editRecordDescriptor); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("patientIndex", patientIndex) + .add("recordIndex", recordIndex) + .add("editRecordDescriptor", editRecordDescriptor) + .toString(); + } + + /** + * Stores the details to edit the record with. Each non-empty field value will + * replace the + * corresponding field value of the record. + */ + public static class EditRecordDescriptor { + private DateTime dateTime; + private List conditions; + private Path filePath; + private Integer patientIndex; + private List medications; + + public EditRecordDescriptor() { + } + + /** + * Copy constructor. + * Defensive copies of {@code conditions} and {@code medications} are used + * internally. + */ + public EditRecordDescriptor(EditRecordCommand.EditRecordDescriptor toCopy) { + setDateTime(toCopy.dateTime); + setConditions(toCopy.conditions); + setFilePath(toCopy.filePath); + setPatientIndex(toCopy.patientIndex); + setMedications(toCopy.medications); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(dateTime, conditions, filePath, medications); + } + + public void setDateTime(DateTime dateTime) { + this.dateTime = dateTime; + } + + public void setFilePath(Path filePath) { + this.filePath = filePath; + } + + public Optional getFilePath() { + return Optional.ofNullable(filePath); + } + + public Optional getPatientIndex() { + return Optional.ofNullable(patientIndex); + } + + public void setPatientIndex(Integer patientIndex) { + this.patientIndex = patientIndex; + } + + public Optional getDateTime() { + return Optional.ofNullable(dateTime); + } + + /** + * Sets {@code conditions} to this record's {@code conditions}. + * A defensive copy of {@code condtions} is used internally. + */ + public void setConditions(List conditions) { + this.conditions = (conditions != null) ? new ArrayList<>(conditions) : null; + } + + /** + * Returns an unmodifiable condition list, which throws + * {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code conditions} is null. + */ + public Optional> getConditions() { + return (conditions != null) ? Optional.of(Collections.unmodifiableList(conditions)) : Optional.empty(); + } + + /** + * Sets {@code medications} to this record's {@code medications}. + * A defensive copy of {@code medications} is used internally. + */ + public void setMedications(List medications) { + this.medications = (medications != null) ? new ArrayList<>(medications) : null; + } + + /** + * Returns an unmodifiable medication list, which throws + * {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code medications} is null. + */ + public Optional> getMedications() { + return (medications != null) ? Optional.of(Collections.unmodifiableList(medications)) : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditRecordCommand.EditRecordDescriptor)) { + return false; + } + + EditRecordCommand.EditRecordDescriptor otherEditRecordDescriptor = + (EditRecordCommand.EditRecordDescriptor) other; + return Objects.equals(dateTime, otherEditRecordDescriptor.dateTime) + && Objects.equals(conditions, otherEditRecordDescriptor.conditions) + && Objects.equals(filePath, otherEditRecordDescriptor.filePath) + && Objects.equals(patientIndex, otherEditRecordDescriptor.patientIndex) + && Objects.equals(medications, otherEditRecordDescriptor.medications); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("dateTime", dateTime) + .add("conditions", conditions) + .add("medications", medications) + .add("filePath", filePath) + .add("patientIndex", patientIndex) + .toString(); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index 3dd85a8ba90..eb7560413f9 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -13,7 +13,7 @@ public class ExitCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); + return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, false, true); } } diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index 72b9eddd3a7..7bc13a3f4ce 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -8,12 +8,13 @@ import seedu.address.model.person.NameContainsKeywordsPredicate; /** - * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Finds and lists all persons in address book whose name contains any of the + * argument keywords. * Keyword matching is case insensitive. */ public class FindCommand extends Command { - public static final String COMMAND_WORD = "find"; + public static final String COMMAND_WORD = "search"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" diff --git a/src/main/java/seedu/address/logic/commands/FindRecordCommand.java b/src/main/java/seedu/address/logic/commands/FindRecordCommand.java new file mode 100644 index 00000000000..e71ac50a92b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindRecordCommand.java @@ -0,0 +1,53 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.record.RecordContainsKeywordsPredicate; + + +/** + * Finds and lists all records of a patient in address book that contain any of the arguement keywords. + * Keyword matching is not case sensitive. + */ +public class FindRecordCommand extends Command { + public static final String COMMAND_WORD = "searchrecord"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all records of a patient that contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers \n" + + "Parameters: KEYWORD [MORE_KEYWORDS]... \n" + + "Example: " + COMMAND_WORD + "Tylenol"; + private final RecordContainsKeywordsPredicate predicate; + public FindRecordCommand(RecordContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredRecordList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_RECORDS_LISTED_OVERVIEW, model.getFilteredRecordList().size()) + ); + } + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (!(other instanceof FindRecordCommand)) { + return false; + } + + FindRecordCommand otherFindRecordCommand = (FindRecordCommand) other; + return this.predicate.equals(otherFindRecordCommand.predicate); + } + @Override + public String toString() { + return new ToStringBuilder(this) + .add("predicate", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index bf824f91bd0..07d26e2a23c 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -16,6 +16,6 @@ public class HelpCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(SHOWING_HELP_MESSAGE, true, false); + return new CommandResult(SHOWING_HELP_MESSAGE, true, false, false); } } diff --git a/src/main/java/seedu/address/logic/commands/PinCommand.java b/src/main/java/seedu/address/logic/commands/PinCommand.java new file mode 100644 index 00000000000..ed6e80bdfdd --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/PinCommand.java @@ -0,0 +1,73 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; + +/** + * Pins the person identified using it's displayed index from the address book. + */ +public class PinCommand extends Command { + + public static final String COMMAND_WORD = "pin"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Pins the person identified by the index number used in the displayed person list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_PIN_PERSON_SUCCESS = "Pinned Person: %1$s"; + + private final Index targetIndex; + + public PinCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person personToPin = lastShownList.get(targetIndex.getZeroBased()); + Person pinnedPerson = new Person(personToPin.getName(), personToPin.getNric(), personToPin.getEmail(), + personToPin.getPhone(), personToPin.getGender(), personToPin.getAge(), personToPin.getBloodType(), + personToPin.getAllergies(), personToPin.getRecords(), personToPin.getAppointments(), true); + + model.setPerson(personToPin, pinnedPerson); + return new CommandResult(String.format(MESSAGE_PIN_PERSON_SUCCESS, Messages.format(personToPin))); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PinCommand)) { + return false; + } + + PinCommand otherPinCommand = (PinCommand) other; + return targetIndex.equals(otherPinCommand.targetIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndex) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/UnpinCommand.java b/src/main/java/seedu/address/logic/commands/UnpinCommand.java new file mode 100644 index 00000000000..d4ed01e380c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UnpinCommand.java @@ -0,0 +1,75 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; + +/** + * Unpins the person identified using it's displayed index from the address + * book. + */ +public class UnpinCommand extends Command { + + public static final String COMMAND_WORD = "unpin"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Unpins the person identified by the index number used in the displayed pinned list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_UNPIN_PERSON_SUCCESS = "Unpinned Person: %1$s"; + + private final Index targetIndex; + + public UnpinCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownPinnedList = model.getPinnedPersonList(); + + if (targetIndex.getZeroBased() >= lastShownPinnedList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person personToUnpin = lastShownPinnedList.get(targetIndex.getZeroBased()); + Person unpinnedPerson = new Person(personToUnpin.getName(), personToUnpin.getNric(), personToUnpin.getEmail(), + personToUnpin.getPhone(), personToUnpin.getGender(), personToUnpin.getAge(), + personToUnpin.getBloodType(), personToUnpin.getAllergies(), personToUnpin.getRecords(), + personToUnpin.getAppointments(), false); + + model.setPerson(personToUnpin, unpinnedPerson); + return new CommandResult(String.format(MESSAGE_UNPIN_PERSON_SUCCESS, Messages.format(personToUnpin))); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UnpinCommand)) { + return false; + } + + UnpinCommand otherUnpinCommand = (UnpinCommand) other; + return targetIndex.equals(otherUnpinCommand.targetIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndex) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ViewAppointmentCommand.java b/src/main/java/seedu/address/logic/commands/ViewAppointmentCommand.java new file mode 100644 index 00000000000..1086a75403f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ViewAppointmentCommand.java @@ -0,0 +1,21 @@ +package seedu.address.logic.commands; + +import seedu.address.model.Model; + +/** + * Opens the Appointments window. + */ +public class ViewAppointmentCommand extends Command { + + public static final String COMMAND_WORD = "viewappointment"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Opens Appointments window.\n" + + "Example: " + COMMAND_WORD; + + public static final String SHOWING_APPOINTMENTS_MESSAGE = "Opened appointments window."; + + @Override + public CommandResult execute(Model model) { + return new CommandResult(SHOWING_APPOINTMENTS_MESSAGE, false, true, false); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ViewCommand.java b/src/main/java/seedu/address/logic/commands/ViewCommand.java new file mode 100644 index 00000000000..6784cb63471 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ViewCommand.java @@ -0,0 +1,69 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; + +/** + * Views patient detailed information at the specified index + * and list medical records of the patient. + */ + +public class ViewCommand extends Command { + + public static final String COMMAND_WORD = "view"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": display the detailed information of the patient identified by the index given" + + " and list all the existing medical records of patient.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_VIEW_PERSON_SUCCESS = "View Patient: %1$s"; + + private final Index targetIndex; + + /** + * Constructor for ViewCommand class + * + * @param targetIndex the patient Index to view + */ + public ViewCommand(Index targetIndex) { + requireNonNull(targetIndex); + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person patientToView = lastShownList.get(targetIndex.getZeroBased()); + model.updateRecordList(patientToView); + return new CommandResult(String.format(MESSAGE_VIEW_PERSON_SUCCESS, Messages.format(patientToView))); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof ViewCommand)) { + return false; + } + + ViewCommand v = (ViewCommand) other; + return this.targetIndex.equals(v.targetIndex); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddAppointmentCommandParser.java b/src/main/java/seedu/address/logic/parser/AddAppointmentCommandParser.java new file mode 100644 index 00000000000..4e9db2b4cf7 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddAppointmentCommandParser.java @@ -0,0 +1,65 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.AddAppointmentCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.appointment.Appointment; +import seedu.address.model.shared.DateTime; +import seedu.address.model.shared.Name; + +/** + * Parses input arguments and creates a new AddAppointmentCommand object + */ +public class AddAppointmentCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the + * AddAppointmentCommand + * and returns an AddAppointmentCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public AddAppointmentCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_DATE); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddAppointmentCommand.MESSAGE_USAGE), pe); + } + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_DATE)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddAppointmentCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_DATE); + + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + DateTime dateTime = ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_DATE).get()); + + Appointment appointment = new Appointment(name, dateTime, null); + + return new AddAppointmentCommand(index, appointment); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values + * in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 4ff1a97ed77..717f1f39d9c 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -1,23 +1,31 @@ package seedu.address.logic.parser; import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_AGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ALLERGIES; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BLOODTYPE; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NRIC; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import java.util.Set; import java.util.stream.Stream; import seedu.address.logic.commands.AddCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; +import seedu.address.model.appointment.UniqueAppointmentList; +import seedu.address.model.person.Age; +import seedu.address.model.person.Allergy; +import seedu.address.model.person.BloodType; import seedu.address.model.person.Email; -import seedu.address.model.person.Name; +import seedu.address.model.person.Gender; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.record.UniqueRecordList; +import seedu.address.model.shared.Name; +import seedu.address.model.shared.Nric; /** * Parses input arguments and creates a new AddCommand object @@ -27,31 +35,40 @@ public class AddCommandParser implements Parser { /** * Parses the given {@code String} of arguments in the context of the AddCommand * and returns an AddCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public AddCommand parse(String args) throws ParseException { - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_NRIC, PREFIX_EMAIL, + PREFIX_PHONE, PREFIX_GENDER, PREFIX_AGE, + PREFIX_BLOODTYPE, PREFIX_ALLERGIES); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_NRIC, PREFIX_EMAIL, PREFIX_PHONE, PREFIX_GENDER, + PREFIX_EMAIL, PREFIX_AGE, PREFIX_BLOODTYPE) || !argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_NRIC, PREFIX_EMAIL, PREFIX_PHONE, PREFIX_GENDER, + PREFIX_EMAIL, PREFIX_AGE, PREFIX_BLOODTYPE); Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); - Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); + Nric nric = ParserUtil.parseNric(argMultimap.getValue(PREFIX_NRIC).get()); Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); - Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); - Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); + Gender gender = ParserUtil.parseGender(argMultimap.getValue(PREFIX_GENDER).get()); + Age age = ParserUtil.parseAge(argMultimap.getValue(PREFIX_AGE).get()); + BloodType bloodType = ParserUtil.parseBloodType(argMultimap.getValue(PREFIX_BLOODTYPE).get()); + Set allergies = ParserUtil.parseAllergies(argMultimap.getAllValues(PREFIX_ALLERGIES)); - Person person = new Person(name, phone, email, address, tagList); + Person person = new Person(name, nric, email, phone, gender, age, bloodType, allergies, + new UniqueRecordList(), new UniqueAppointmentList(), false); return new AddCommand(person); } /** - * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * Returns true if none of the prefixes contains empty {@code Optional} values + * in the given * {@code ArgumentMultimap}. */ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { diff --git a/src/main/java/seedu/address/logic/parser/AddRecordCommandParser.java b/src/main/java/seedu/address/logic/parser/AddRecordCommandParser.java new file mode 100644 index 00000000000..4f72e9333bd --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddRecordCommandParser.java @@ -0,0 +1,53 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONDITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MEDICATION; + +import java.util.List; +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.AddRecordCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.record.Condition; +import seedu.address.model.record.Medication; +import seedu.address.model.record.Record; +import seedu.address.model.shared.DateTime; + +/** + * Parses a user input and creates a AddRecordCommand object + */ +public class AddRecordCommandParser implements Parser { + @Override + public AddRecordCommand parse(String userInput) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(userInput, PREFIX_DATE, PREFIX_CONDITION, PREFIX_MEDICATION); + + if (!arePrefixesPresent(argMultimap, PREFIX_DATE, PREFIX_CONDITION, PREFIX_MEDICATION) + || argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddRecordCommand.MESSAGE_USAGE)); + } + + Index index = ParserUtil.parseIndex(argMultimap.getPreamble()); + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_DATE); + + DateTime dateTime = ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_DATE).get()); + List conditions = ParserUtil.parseConditions(argMultimap.getAllValues(PREFIX_CONDITION)); + List medications = ParserUtil.parseMedications(argMultimap.getAllValues(PREFIX_MEDICATION)); + + Record record = new Record(dateTime, conditions, medications, null, index.getZeroBased()); + return new AddRecordCommand(index, record); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values + * in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 3149ee07e0b..8af52a80639 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -8,15 +8,24 @@ import java.util.regex.Pattern; import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.commands.AddAppointmentCommand; import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; +import seedu.address.logic.commands.AddRecordCommand; import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.DeleteAppointmentCommand; import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeleteRecordCommand; import seedu.address.logic.commands.EditCommand; +import seedu.address.logic.commands.EditRecordCommand; import seedu.address.logic.commands.ExitCommand; import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FindRecordCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.PinCommand; +import seedu.address.logic.commands.UnpinCommand; +import seedu.address.logic.commands.ViewAppointmentCommand; +import seedu.address.logic.commands.ViewCommand; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -33,9 +42,11 @@ public class AddressBookParser { /** * Parses user input into command for execution. * - * @param userInput full user input string + * @param userInput + * full user input string * @return the command based on the user input - * @throws ParseException if the user input does not conform the expected format + * @throws ParseException + * if the user input does not conform the expected format */ public Command parseCommand(String userInput) throws ParseException { final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); @@ -46,7 +57,8 @@ public Command parseCommand(String userInput) throws ParseException { final String commandWord = matcher.group("commandWord"); final String arguments = matcher.group("arguments"); - // Note to developers: Change the log level in config.json to enable lower level (i.e., FINE, FINER and lower) + // Note to developers: Change the log level in config.json to enable lower level + // (i.e., FINE, FINER and lower) // log messages such as the one below. // Lower level log messages are used sparingly to minimize noise in the code. logger.fine("Command word: " + commandWord + "; Arguments: " + arguments); @@ -56,18 +68,45 @@ public Command parseCommand(String userInput) throws ParseException { case AddCommand.COMMAND_WORD: return new AddCommandParser().parse(arguments); + case AddRecordCommand.COMMAND_WORD: + return new AddRecordCommandParser().parse(arguments); + case EditCommand.COMMAND_WORD: return new EditCommandParser().parse(arguments); + case EditRecordCommand.COMMAND_WORD: + return new EditRecordCommandParser().parse(arguments); + case DeleteCommand.COMMAND_WORD: return new DeleteCommandParser().parse(arguments); - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); + case DeleteRecordCommand.COMMAND_WORD: + return new DeleteRecordCommandParser().parse(arguments); + + case PinCommand.COMMAND_WORD: + return new PinCommandParser().parse(arguments); + + case UnpinCommand.COMMAND_WORD: + return new UnpinCommandParser().parse(arguments); + + case AddAppointmentCommand.COMMAND_WORD: + return new AddAppointmentCommandParser().parse(arguments); + + case DeleteAppointmentCommand.COMMAND_WORD: + return new DeleteAppointmentCommandParser().parse(arguments); + + case ViewCommand.COMMAND_WORD: + return new ViewCommandParser().parse(arguments); + + case ViewAppointmentCommand.COMMAND_WORD: + return new ViewAppointmentCommand(); case FindCommand.COMMAND_WORD: return new FindCommandParser().parse(arguments); + case FindRecordCommand.COMMAND_WORD: + return new FindRecordCommandParser().parse(arguments); + case ListCommand.COMMAND_WORD: return new ListCommand(); diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..58b6c912aa8 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -7,9 +7,14 @@ public class CliSyntax { /* Prefix definitions */ public static final Prefix PREFIX_NAME = new Prefix("n/"); - public static final Prefix PREFIX_PHONE = new Prefix("p/"); + public static final Prefix PREFIX_NRIC = new Prefix("i/"); public static final Prefix PREFIX_EMAIL = new Prefix("e/"); - public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); - + public static final Prefix PREFIX_PHONE = new Prefix("p/"); + public static final Prefix PREFIX_GENDER = new Prefix("g/"); + public static final Prefix PREFIX_AGE = new Prefix("a/"); + public static final Prefix PREFIX_BLOODTYPE = new Prefix("bt/"); + public static final Prefix PREFIX_ALLERGIES = new Prefix("al/"); + public static final Prefix PREFIX_DATE = new Prefix("d/"); + public static final Prefix PREFIX_CONDITION = new Prefix("c/"); + public static final Prefix PREFIX_MEDICATION = new Prefix("m/"); } diff --git a/src/main/java/seedu/address/logic/parser/DeleteAppointmentCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteAppointmentCommandParser.java new file mode 100644 index 00000000000..cfda28809a2 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteAppointmentCommandParser.java @@ -0,0 +1,31 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.DeleteAppointmentCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteAppointmentCommand object + */ +public class DeleteAppointmentCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the + * DeleteAppointmentCommand + * and returns a DeleteAppointmentCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteAppointmentCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteAppointmentCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteAppointmentCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteRecordCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteRecordCommandParser.java new file mode 100644 index 00000000000..b3185c660d0 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteRecordCommandParser.java @@ -0,0 +1,32 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.DeleteRecordCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteRecordCommand object + */ +public class DeleteRecordCommandParser implements Parser { + + @Override + public DeleteRecordCommand parse(String userInput) throws ParseException { + requireNonNull(userInput); + Index patientIndex; + Index recordIndex; + + try { + String trimmedUserInput = userInput.trim(); + patientIndex = ParserUtil.parsePatientIndex(trimmedUserInput); + recordIndex = ParserUtil.parseRecordIndex(trimmedUserInput); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteRecordCommand.MESSAGE_USAGE), pe); + } + + return new DeleteRecordCommand(patientIndex, recordIndex); + } +} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 46b3309a78b..c91d3f676bd 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -2,11 +2,13 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_AGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ALLERGIES; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BLOODTYPE; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import java.util.Collection; import java.util.Collections; @@ -17,7 +19,7 @@ import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.tag.Tag; +import seedu.address.model.person.Allergy; /** * Parses input arguments and creates a new EditCommand object @@ -25,14 +27,17 @@ public class EditCommandParser implements Parser { /** - * Parses the given {@code String} of arguments in the context of the EditCommand + * Parses the given {@code String} of arguments in the context of the + * EditCommand * and returns an EditCommand object for execution. - * @throws ParseException if the user input does not conform the expected format + * + * @throws ParseException + * if the user input does not conform the expected format */ public EditCommand parse(String args) throws ParseException { requireNonNull(args); - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_EMAIL, PREFIX_PHONE, + PREFIX_GENDER, PREFIX_AGE, PREFIX_BLOODTYPE, PREFIX_ALLERGIES); Index index; @@ -42,23 +47,30 @@ public EditCommand parse(String args) throws ParseException { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); } - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_EMAIL, PREFIX_PHONE, + PREFIX_GENDER, PREFIX_AGE, PREFIX_BLOODTYPE); EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); if (argMultimap.getValue(PREFIX_NAME).isPresent()) { editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); } + if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { + editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); + } if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); } - if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); + if (argMultimap.getValue(PREFIX_GENDER).isPresent()) { + editPersonDescriptor.setGender(ParserUtil.parseGender(argMultimap.getValue(PREFIX_GENDER).get())); + } + if (argMultimap.getValue(PREFIX_AGE).isPresent()) { + editPersonDescriptor.setAge(ParserUtil.parseAge(argMultimap.getValue(PREFIX_AGE).get())); } - if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); + if (argMultimap.getValue(PREFIX_BLOODTYPE).isPresent()) { + editPersonDescriptor.setBloodType(ParserUtil.parseBloodType(argMultimap.getValue(PREFIX_BLOODTYPE).get())); } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); + parseAllergiesForEdit(argMultimap.getAllValues(PREFIX_ALLERGIES)).ifPresent(editPersonDescriptor::setAllergies); if (!editPersonDescriptor.isAnyFieldEdited()) { throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); @@ -68,18 +80,21 @@ public EditCommand parse(String args) throws ParseException { } /** - * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. - * If {@code tags} contain only one element which is an empty string, it will be parsed into a - * {@code Set} containing zero tags. + * Parses {@code Collection allergies} into a {@code Set} if + * {@code allergies} is non-empty. + * If {@code allergies} contain only one element which is an empty string, it + * will be parsed into a + * {@code Set} containing zero allergies. */ - private Optional> parseTagsForEdit(Collection tags) throws ParseException { - assert tags != null; + private Optional> parseAllergiesForEdit(Collection allergies) throws ParseException { + assert allergies != null; - if (tags.isEmpty()) { + if (allergies.isEmpty()) { return Optional.empty(); } - Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; - return Optional.of(ParserUtil.parseTags(tagSet)); + Collection allergySet = allergies.size() == 1 + && allergies.contains("") ? Collections.emptySet() : allergies; + return Optional.of(ParserUtil.parseAllergies(allergySet)); } } diff --git a/src/main/java/seedu/address/logic/parser/EditRecordCommandParser.java b/src/main/java/seedu/address/logic/parser/EditRecordCommandParser.java new file mode 100644 index 00000000000..e239143774f --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/EditRecordCommandParser.java @@ -0,0 +1,97 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONDITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MEDICATION; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.EditCommand; +import seedu.address.logic.commands.EditRecordCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.record.Condition; +import seedu.address.model.record.Medication; + +/** + * Parses input arguments and creates a new EditRecordCommand object + */ +public class EditRecordCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditCommand + * and returns an EditCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditRecordCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_DATE, PREFIX_CONDITION, PREFIX_MEDICATION); + + Index patientIndex; + Index recordIndex; + + try { + String preamble = argMultimap.getPreamble(); + patientIndex = ParserUtil.parsePatientIndex(preamble); + recordIndex = ParserUtil.parseRecordIndex(preamble); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EditRecordCommand.MESSAGE_USAGE), pe); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_DATE); + + EditRecordCommand.EditRecordDescriptor editRecordDescriptor = new EditRecordCommand.EditRecordDescriptor(); + + if (argMultimap.getValue(PREFIX_DATE).isPresent()) { + editRecordDescriptor.setDateTime(ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_DATE).get())); + } + parseConditionsForEdit(argMultimap.getAllValues(PREFIX_CONDITION)) + .ifPresent(editRecordDescriptor::setConditions); + + parseMedicationForEdit(argMultimap.getAllValues(PREFIX_MEDICATION)) + .ifPresent(editRecordDescriptor::setMedications); + + if (!editRecordDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); + } + + return new EditRecordCommand(patientIndex, recordIndex, editRecordDescriptor); + } + + /** + * Parses {@code Collection condtions} into a {@code Set} if {@code condition} is non-empty. + * If {@code contion} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero allergies. + */ + private Optional> parseConditionsForEdit(Collection conditions) throws ParseException { + assert conditions != null; + + if (conditions.isEmpty()) { + return Optional.empty(); + } + Collection conditionSet = conditions.size() == 1 + && conditions.contains("") ? Collections.emptySet() : conditions; + return Optional.of(ParserUtil.parseConditions(conditionSet)); + } + /** + * Parses {@code Collection medications} into a {@code Set} if {@code medications} is non-empty. + * If {@code medications} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero allergies. + */ + private Optional> parseMedicationForEdit(Collection medications) throws ParseException { + assert medications != null; + if (medications.isEmpty()) { + return Optional.empty(); + } + Collection medicationSet = medications.size() == 1 + && medications.contains("") ? Collections.emptySet() : medications; + return Optional.of(ParserUtil.parseMedications(medicationSet)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindRecordCommandParser.java b/src/main/java/seedu/address/logic/parser/FindRecordCommandParser.java new file mode 100644 index 00000000000..4a167eee5a9 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindRecordCommandParser.java @@ -0,0 +1,26 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.address.logic.commands.FindRecordCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.record.RecordContainsKeywordsPredicate; + + +/** + * Parses input arguments and creates a new FindRecordCommand object + */ +public class FindRecordCommandParser implements Parser { + @Override + public FindRecordCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindRecordCommand.MESSAGE_USAGE)); + } + String[] keywords = trimmedArgs.split("\\s+"); + return new FindRecordCommand(new RecordContainsKeywordsPredicate(Arrays.asList(keywords))); + } +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..82b6716941c 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -2,30 +2,45 @@ import static java.util.Objects.requireNonNull; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Set; import seedu.address.commons.core.index.Index; import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; +import seedu.address.model.person.Age; +import seedu.address.model.person.Allergy; +import seedu.address.model.person.BloodType; import seedu.address.model.person.Email; -import seedu.address.model.person.Name; +import seedu.address.model.person.Gender; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.record.Condition; +import seedu.address.model.record.Medication; +import seedu.address.model.shared.DateTime; +import seedu.address.model.shared.Name; +import seedu.address.model.shared.Nric; /** - * Contains utility methods used for parsing strings in the various *Parser classes. + * Contains utility methods used for parsing strings in the various *Parser + * classes. */ public class ParserUtil { + public static final String MESSAGE_INVALID_INPUT = "Index input is not in the format of number/number"; public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; + public static final String MESSAGE_INVALID_PATIENT_INDEX = "Patient index is not a non-zero unsigned integer."; + public static final String MESSAGE_INVALID_RECORD_INDEX = "Record index is not a non-zero unsigned integer."; /** - * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be + * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading + * and trailing whitespaces will be * trimmed. - * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). + * + * @throws ParseException if the specified index is invalid (not non-zero + * unsigned integer). */ public static Index parseIndex(String oneBasedIndex) throws ParseException { String trimmedIndex = oneBasedIndex.trim(); @@ -35,6 +50,42 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException { return Index.fromOneBased(Integer.parseInt(trimmedIndex)); } + /** + * Parses {@code oneBasedIndexes} for edit record command into a patient's + * {@code Index} + * and returns it. Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the specified patient index is invalid (not + * non-zero unsigned integer). + */ + public static Index parsePatientIndex(String oneBasedIndexes) throws ParseException { + // Check if input matches the format of two numbers separated by a slash + if (!oneBasedIndexes.matches("\\d+/\\d+")) { + throw new ParseException(MESSAGE_INVALID_INPUT); + } + String trimmedIndex = oneBasedIndexes.split("/")[0].trim(); + if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { + throw new ParseException(MESSAGE_INVALID_PATIENT_INDEX); + } + return Index.fromOneBased(Integer.parseInt(trimmedIndex)); + } + + /** + * Parses {@code oneBasedIndexes} for edit record command into a record's + * {@code Index} + * and returns it. Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the specified patient index is invalid (not + * non-zero unsigned integer). + */ + public static Index parseRecordIndex(String oneBasedIndexes) throws ParseException { + String trimmedIndex = oneBasedIndexes.split("/")[1].trim(); + if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { + throw new ParseException(MESSAGE_INVALID_RECORD_INDEX); + } + return Index.fromOneBased(Integer.parseInt(trimmedIndex)); + } + /** * Parses a {@code String name} into a {@code Name}. * Leading and trailing whitespaces will be trimmed. @@ -50,6 +101,21 @@ public static Name parseName(String name) throws ParseException { return new Name(trimmedName); } + /** + * Parses a {@code String nric} into a {@code Nric}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code nric} is invalid. + */ + public static Nric parseNric(String nric) throws ParseException { + requireNonNull(nric); + String trimmedNric = nric.trim(); + if (!Nric.isValidNric(trimmedNric)) { + throw new ParseException(Nric.MESSAGE_CONSTRAINTS); + } + return new Nric(trimmedNric.toUpperCase()); + } + /** * Parses a {@code String phone} into a {@code Phone}. * Leading and trailing whitespaces will be trimmed. @@ -66,18 +132,85 @@ public static Phone parsePhone(String phone) throws ParseException { } /** - * Parses a {@code String address} into an {@code Address}. + * Parses a {@code String gender} into an {@code Gender}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code gender} is invalid. + */ + public static Gender parseGender(String gender) throws ParseException { + requireNonNull(gender); + String trimmedGender = gender.trim(); + if (!Gender.isValidGender(trimmedGender)) { + throw new ParseException(Gender.MESSAGE_CONSTRAINTS); + } + return new Gender(trimmedGender); + } + + /** + * Parses a {@code String age} into an {@code Age}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code address} is invalid. + * @throws ParseException if the given {@code age} is invalid. */ - public static Address parseAddress(String address) throws ParseException { - requireNonNull(address); - String trimmedAddress = address.trim(); - if (!Address.isValidAddress(trimmedAddress)) { - throw new ParseException(Address.MESSAGE_CONSTRAINTS); + public static Age parseAge(String age) throws ParseException { + requireNonNull(age); + int trimmedAge = 0; + try { + trimmedAge = Integer.parseInt(age.trim()); + + } catch (NumberFormatException e) { + throw new ParseException(Age.MESSAGE_CONSTRAINTS); } - return new Address(trimmedAddress); + if (!Age.isValidAge(trimmedAge)) { + throw new ParseException(Age.MESSAGE_CONSTRAINTS); + } + + return new Age(trimmedAge); + } + + /** + * Parses a {@code String bloodType} into an {@code BloodType}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code allergy} is invalid. + */ + public static BloodType parseBloodType(String bloodType) throws ParseException { + requireNonNull(bloodType); + String trimmedBloodType = bloodType.trim(); + if (!BloodType.isValidBloodType(trimmedBloodType)) { + throw new ParseException(BloodType.MESSAGE_CONSTRAINTS); + } + return new BloodType(trimmedBloodType); + } + + /** + * Parses a {@code String allergy} into an {@code Allergy}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code allergy} is invalid. + */ + public static Allergy parseAllergy(String allergy) throws ParseException { + requireNonNull(allergy); + String trimmedAllergy = allergy.trim(); + if (!Allergy.isValidAllergy(trimmedAllergy)) { + throw new ParseException(Allergy.MESSAGE_CONSTRAINTS); + } + return new Allergy(trimmedAllergy); + } + + /** + * Parses a {@code Collection allergies} into an {@code Set}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code allergy} is invalid. + */ + public static Set parseAllergies(Collection allergies) throws ParseException { + requireNonNull(allergies); + final Set allergiesSet = new HashSet<>(); + for (String allergy : allergies) { + allergiesSet.add(parseAllergy(allergy)); + } + return allergiesSet; } /** @@ -96,29 +229,79 @@ public static Email parseEmail(String email) throws ParseException { } /** - * Parses a {@code String tag} into a {@code Tag}. + * Parses a {@code String dateTime} into an {@code DateTime}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code tag} is invalid. + * @throws ParseException if the given {@code dateTime} is invalid. */ - public static Tag parseTag(String tag) throws ParseException { - requireNonNull(tag); - String trimmedTag = tag.trim(); - if (!Tag.isValidTagName(trimmedTag)) { - throw new ParseException(Tag.MESSAGE_CONSTRAINTS); + public static DateTime parseDateTime(String dateTime) throws ParseException { + requireNonNull(dateTime); + String trimmedDateTime = dateTime.trim(); + if (!DateTime.isValidDateTime(trimmedDateTime)) { + throw new ParseException(DateTime.MESSAGE_CONSTRAINTS); } - return new Tag(trimmedTag); + return new DateTime(trimmedDateTime); } /** - * Parses {@code Collection tags} into a {@code Set}. + * Parses a {@code String condition} into an {@code Condition}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code condition} is invalid. + */ + public static Condition parseCondition(String condition) throws ParseException { + requireNonNull(condition); + String trimmedCondition = condition.trim(); + if (!Condition.isValidCondition(trimmedCondition)) { + throw new ParseException(Condition.MESSAGE_CONSTRAINTS); + } + return new Condition(trimmedCondition); + } + + /** + * Parses a {@code Collection condtions} into an + * {@code List}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code condition} is invalid. + */ + public static List parseConditions(Collection conditions) throws ParseException { + requireNonNull(conditions); + final List conditionsList = new ArrayList<>(); + for (String condition : conditions) { + conditionsList.add(parseCondition(condition)); + } + return conditionsList; + } + + /** + * Parses a {@code String medication} into an {@code Medication}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code medication} is invalid. + */ + public static Medication parseMedication(String medication) throws ParseException { + requireNonNull(medication); + String trimmedMedication = medication.trim(); + if (!Medication.isValidMedication(trimmedMedication)) { + throw new ParseException(Medication.MESSAGE_CONSTRAINTS); + } + return new Medication(trimmedMedication); + } + + /** + * Parses a {@code Collection medications} into an + * {@code List}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code medication} is invalid. */ - public static Set parseTags(Collection tags) throws ParseException { - requireNonNull(tags); - final Set tagSet = new HashSet<>(); - for (String tagName : tags) { - tagSet.add(parseTag(tagName)); + public static List parseMedications(Collection medications) throws ParseException { + requireNonNull(medications); + final List medicationList = new ArrayList<>(); + for (String medication : medications) { + medicationList.add(parseMedication(medication)); } - return tagSet; + return medicationList; } } diff --git a/src/main/java/seedu/address/logic/parser/PinCommandParser.java b/src/main/java/seedu/address/logic/parser/PinCommandParser.java new file mode 100644 index 00000000000..8ed7846388a --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/PinCommandParser.java @@ -0,0 +1,29 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.PinCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new PinCommand object + */ +public class PinCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the PinCommand + * and returns a PinCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public PinCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new PinCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, PinCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/UnpinCommandParser.java b/src/main/java/seedu/address/logic/parser/UnpinCommandParser.java new file mode 100644 index 00000000000..462f994f6f6 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/UnpinCommandParser.java @@ -0,0 +1,29 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.UnpinCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new UnpinCommand object + */ +public class UnpinCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the UnpinCommand + * and returns a UnpinCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public UnpinCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new UnpinCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnpinCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ViewCommandParser.java b/src/main/java/seedu/address/logic/parser/ViewCommandParser.java new file mode 100644 index 00000000000..5e925cc01b0 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ViewCommandParser.java @@ -0,0 +1,27 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.ViewCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parse input and create a View Command. + */ + +public class ViewCommandParser implements Parser { + + @Override + public ViewCommand parse(String args) throws ParseException { + + Index index; + try { + index = ParserUtil.parseIndex(args); + return new ViewCommand(index); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ViewCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 73397161e84..bfd3ac50715 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -1,13 +1,20 @@ package seedu.address.model; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import java.util.ArrayList; import java.util.List; import javafx.collections.ObservableList; +import seedu.address.commons.core.index.Index; import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.appointment.Appointment; +import seedu.address.model.appointment.UniqueAppointmentList; import seedu.address.model.person.Person; import seedu.address.model.person.UniquePersonList; +import seedu.address.model.record.Record; +import seedu.address.model.record.UniqueRecordList; /** * Wraps all data at the address-book level @@ -16,19 +23,29 @@ public class AddressBook implements ReadOnlyAddressBook { private final UniquePersonList persons; + private final UniquePersonList personBeingViewed; + private final UniqueRecordList records; + private final UniqueAppointmentList appointments; /* - * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication - * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html + * The 'unusual' code block below is a non-static initialization block, + * sometimes used to avoid duplication + * between constructors. See + * https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html * - * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication - * among constructors. + * Note that non-static init blocks are not recommended to use. There are other + * ways to avoid duplication + * among constructors. */ { persons = new UniquePersonList(); + records = new UniqueRecordList(); + appointments = new UniqueAppointmentList(); + personBeingViewed = new UniquePersonList(); } - public AddressBook() {} + public AddressBook() { + } /** * Creates an AddressBook using the Persons in the {@code toBeCopied} @@ -48,25 +65,42 @@ public void setPersons(List persons) { this.persons.setPersons(persons); } + public void setRecords(Person person) { + ArrayList beingViewed = new ArrayList<>(); + beingViewed.add(person); + this.personBeingViewed.setPersons(beingViewed); + this.records.setRecords(person.getRecords()); + } + /** * Resets the existing data of this {@code AddressBook} with {@code newData}. */ public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); - setPersons(newData.getPersonList()); + List fullList = newData.getPersonList(); + setPersons(fullList); } //// person-level operations /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Returns true if a person with the same identity as {@code person} exists in + * the address book. */ public boolean hasPerson(Person person) { requireNonNull(person); return persons.contains(person); } + /** + * Returns true if the same record under the patient at {@code index} exists in the Medbook. + */ + public boolean hasRecord(Record record, Index index) { + requireAllNonNull(record, index); + return persons.asUnmodifiableObservableList().get(index.getZeroBased()).getRecords().contains(record); + } + /** * Adds a person to the address book. * The person must not already exist in the address book. @@ -76,9 +110,11 @@ public void addPerson(Person p) { } /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. + * Replaces the given person {@code target} in the list with + * {@code editedPerson}. * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * The person identity of {@code editedPerson} must not be the same as another + * existing person in the address book. */ public void setPerson(Person target, Person editedPerson) { requireNonNull(editedPerson); @@ -108,6 +144,32 @@ public ObservableList getPersonList() { return persons.asUnmodifiableObservableList(); } + public ObservableList getRecordList() { + return records.asUnmodifiableObservableList(); + } + + public ObservableList getAppointmentList() { + resetAppointmentList(); + return appointments.asUnmodifiableObservableList(); + } + + /** + * Resets the appointment list to include all appointments from all persons. + */ + public void resetAppointmentList() { + UniqueAppointmentList newList = new UniqueAppointmentList(); + for (Person person : persons) { + for (Appointment appointment : person.getAppointments()) { + newList.add(appointment); + } + } + appointments.setAppointments(newList); + } + + public ObservableList getPersonBeingViewed() { + return personBeingViewed.asUnmodifiableObservableList(); + } + @Override public boolean equals(Object other) { if (other == this) { diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..eecccf52f84 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -5,7 +5,10 @@ import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; +import seedu.address.commons.core.index.Index; +import seedu.address.model.appointment.Appointment; import seedu.address.model.person.Person; +import seedu.address.model.record.Record; /** * The API of the Model component. @@ -13,6 +16,10 @@ public interface Model { /** {@code Predicate} that always evaluate to true */ Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + Predicate PREDICATE_SHOW_ALL_RECORDS = unused -> true; + + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_APPOINTMENTS = unused -> true; /** * Replaces user prefs data with the data in {@code userPrefs}. @@ -53,10 +60,16 @@ public interface Model { ReadOnlyAddressBook getAddressBook(); /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Returns true if a person with the same identity as {@code person} exists in + * the address book. */ boolean hasPerson(Person person); + /** + * Returns true if the same record exists under the patient in the Medbook + */ + boolean hasRecord(Record record, Index index); + /** * Deletes the given person. * The person must exist in the address book. @@ -72,7 +85,8 @@ public interface Model { /** * Replaces the given person {@code target} with {@code editedPerson}. * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * The person identity of {@code editedPerson} must not be the same as another + * existing person in the address book. */ void setPerson(Person target, Person editedPerson); @@ -80,8 +94,38 @@ public interface Model { ObservableList getFilteredPersonList(); /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. - * @throws NullPointerException if {@code predicate} is null. + * Updates the filter of the filtered person list to filter by the given + * {@code predicate}. + * + * @throws NullPointerException + * if {@code predicate} is null. */ void updateFilteredPersonList(Predicate predicate); + + /** Returns an unmodifiable view of the pinned person list */ + ObservableList getPinnedPersonList(); + + /** Returns an unmodifiable view of the filtered appointment list */ + ObservableList getAppointmentList(); + + void resetAppointmentList(); + + ObservableList getRecordList(); + + /** Returns an unmodifiable view of the filtered record list */ + ObservableList getFilteredRecordList(); + + void updateRecordList(Person person); + + /** + * Updates the filter of the filtered record list to filter by the given + * {@code predicate}. + * + * @throws NullPointerException + * if {@code predicate} is null. + */ + void updateFilteredRecordList(Predicate predicate); + + ObservableList getPersonBeingViewed(); + } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 57bc563fde6..a9b108ff39c 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -11,17 +11,21 @@ import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.index.Index; +import seedu.address.model.appointment.Appointment; import seedu.address.model.person.Person; +import seedu.address.model.record.Record; /** * Represents the in-memory model of the address book data. */ public class ModelManager implements Model { private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - + private static ModelManager instance; private final AddressBook addressBook; - private final UserPrefs userPrefs; private final FilteredList filteredPersons; + private final FilteredList filteredRecords; + private final UserPrefs userPrefs; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -34,13 +38,23 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs this.addressBook = new AddressBook(addressBook); this.userPrefs = new UserPrefs(userPrefs); filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredRecords = new FilteredList<>(this.addressBook.getRecordList()); + instance = this; } public ModelManager() { this(new AddressBook(), new UserPrefs()); } - //=========== UserPrefs ================================================================================== + public static Model getInstance() { + if (instance == null) { + instance = new ModelManager(/* necessary initializations */); + } + return instance; + } + + // =========== UserPrefs + // ================================================================================== @Override public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { @@ -75,7 +89,8 @@ public void setAddressBookFilePath(Path addressBookFilePath) { userPrefs.setAddressBookFilePath(addressBookFilePath); } - //=========== AddressBook ================================================================================ + // =========== AddressBook + // ================================================================================ @Override public void setAddressBook(ReadOnlyAddressBook addressBook) { @@ -93,6 +108,12 @@ public boolean hasPerson(Person person) { return addressBook.hasPerson(person); } + @Override + public boolean hasRecord(Record record, Index index) { + requireAllNonNull(record, index); + return addressBook.hasRecord(record, index); + } + @Override public void deletePerson(Person target) { addressBook.removePerson(target); @@ -107,14 +128,15 @@ public void addPerson(Person person) { @Override public void setPerson(Person target, Person editedPerson) { requireAllNonNull(target, editedPerson); - addressBook.setPerson(target, editedPerson); } - //=========== Filtered Person List Accessors ============================================================= + // =========== Filtered Person List Accessors + // ============================================================= /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of + * Returns an unmodifiable view of the list of {@code Person} backed by the + * internal list of * {@code versionedAddressBook} */ @Override @@ -128,6 +150,56 @@ public void updateFilteredPersonList(Predicate predicate) { filteredPersons.setPredicate(predicate); } + @Override + public ObservableList getPinnedPersonList() { + FilteredList pinnedPersons = new FilteredList<>(this.addressBook.getPersonList()); + pinnedPersons.setPredicate(person -> person.isPinned()); + return pinnedPersons; + } + + /** + * Returns an unmodifiable view of the list of {@code Person} backed by the + * internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getAppointmentList() { + return this.addressBook.getAppointmentList(); + } + + @Override + public void resetAppointmentList() { + this.addressBook.resetAppointmentList(); + } + + @Override + public ObservableList getRecordList() { + return this.addressBook.getRecordList(); + } + + @Override + public ObservableList getFilteredRecordList() { + return filteredRecords; + } + + @Override + public void updateRecordList(Person person) { + requireNonNull(person); + this.addressBook.setRecords(person); + updateFilteredRecordList(PREDICATE_SHOW_ALL_RECORDS); + } + + @Override + public void updateFilteredRecordList(Predicate predicate) { + requireNonNull(predicate); + filteredRecords.setPredicate(predicate); + } + + @Override + public ObservableList getPersonBeingViewed() { + return this.addressBook.getPersonBeingViewed(); + } + @Override public boolean equals(Object other) { if (other == this) { @@ -142,7 +214,7 @@ public boolean equals(Object other) { ModelManager otherModelManager = (ModelManager) other; return addressBook.equals(otherModelManager.addressBook) && userPrefs.equals(otherModelManager.userPrefs) - && filteredPersons.equals(otherModelManager.filteredPersons); + && filteredPersons.equals(otherModelManager.filteredPersons) + && filteredRecords.equals(otherModelManager.filteredRecords); } - } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 6ddc2cd9a29..6f862be2eca 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -13,5 +13,4 @@ public interface ReadOnlyAddressBook { * This list will not contain any duplicate persons. */ ObservableList getPersonList(); - } diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 6be655fb4c7..47ab6f836a1 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -14,7 +14,7 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path addressBookFilePath = Paths.get("data" , "medbook.json"); /** * Creates a {@code UserPrefs} with default values. diff --git a/src/main/java/seedu/address/model/appointment/Appointment.java b/src/main/java/seedu/address/model/appointment/Appointment.java new file mode 100644 index 00000000000..da2af244f3f --- /dev/null +++ b/src/main/java/seedu/address/model/appointment/Appointment.java @@ -0,0 +1,77 @@ +package seedu.address.model.appointment; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Objects; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.shared.DateTime; +import seedu.address.model.shared.Name; +import seedu.address.model.shared.Nric; + +/** + * Represents an Appointment in the address book. + * Guarantees: details are present and not null, field values are validated, + * immutable. + */ +public class Appointment { + + // Identity fields + private final Name name; + private final DateTime dateTime; + private final Nric nric; + + /** + * Every field must be present and not null. + */ + public Appointment(Name name, DateTime dateTime, Nric nric) { + requireAllNonNull(name, dateTime); + this.name = name; + this.dateTime = dateTime; + this.nric = nric; + } + + public Name getName() { + return name; + } + + public DateTime getDateTime() { + return dateTime; + } + + public Nric getNric() { + return nric; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Appointment)) { + return false; + } + + Appointment otherAppointment = (Appointment) other; + return name.equals(otherAppointment.name) + && dateTime.equals(otherAppointment.dateTime) + && ((nric == null && otherAppointment.nric == null) || nric.equals(otherAppointment.nric)); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(name, dateTime); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("name", name) + .add("dateTime", dateTime) + .add("nric", nric) + .toString(); + } +} diff --git a/src/main/java/seedu/address/model/appointment/UniqueAppointmentList.java b/src/main/java/seedu/address/model/appointment/UniqueAppointmentList.java new file mode 100644 index 00000000000..16f09078353 --- /dev/null +++ b/src/main/java/seedu/address/model/appointment/UniqueAppointmentList.java @@ -0,0 +1,148 @@ +package seedu.address.model.appointment; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.appointment.exceptions.AppointmentNotFoundException; +import seedu.address.model.appointment.exceptions.DuplicateAppointmentException; + +/** + * A list of appointments that enforces uniqueness between its elements and does not allow nulls. + * An appointment is considered unique by comparing using {@code Appointment#equals(Appointment)}. + * + * Supports a minimal set of list operations. + * + * @see Appointment#equals(Appointment) + */ +public class UniqueAppointmentList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent appointment as the given argument. + */ + public boolean contains(Appointment toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::equals); + } + + /** + * Adds a appointment to the list. + * The appointment must not already exist in the list. + */ + public void add(Appointment toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateAppointmentException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the appointment {@code target} in the list with {@code editedappointment}. + * {@code target} must exist in the list. + * The appointment identity of {@code editedappointment} must not be the same as + * another existing appointment in the list. + */ + public void setAppointment(Appointment target, Appointment editedappointment) { + requireAllNonNull(target, editedappointment); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new AppointmentNotFoundException(); + } + + if (!target.equals(editedappointment) && contains(editedappointment)) { + throw new DuplicateAppointmentException(); + } + + internalList.set(index, editedappointment); + } + + /** + * Removes the equivalent appointment from the list. + * The appointment must exist in the list. + */ + public void remove(Appointment toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new AppointmentNotFoundException(); + } + } + + public void setAppointments(UniqueAppointmentList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code appointments}. + * {@code appointments} must not contain duplicate appointments. + */ + public void setAppointments(List appointments) { + requireAllNonNull(appointments); + if (!appointmentsAreUnique(appointments)) { + throw new DuplicateAppointmentException(); + } + + internalList.setAll(appointments); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UniqueAppointmentList)) { + return false; + } + + UniqueAppointmentList otherUniqueAppointmentList = (UniqueAppointmentList) other; + return internalList.equals(otherUniqueAppointmentList.internalList); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + @Override + public String toString() { + return internalList.toString(); + } + + /** + * Returns true if {@code appointments} contains only unique appointments. + */ + private boolean appointmentsAreUnique(List appointments) { + for (int i = 0; i < appointments.size() - 1; i++) { + for (int j = i + 1; j < appointments.size(); j++) { + if (appointments.get(i).equals(appointments.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/appointment/exceptions/AppointmentNotFoundException.java b/src/main/java/seedu/address/model/appointment/exceptions/AppointmentNotFoundException.java new file mode 100644 index 00000000000..5dc5c48d3d8 --- /dev/null +++ b/src/main/java/seedu/address/model/appointment/exceptions/AppointmentNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.appointment.exceptions; + +/** + * Signals that the operation is unable to find the specified appointment. + */ +public class AppointmentNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/appointment/exceptions/DuplicateAppointmentException.java b/src/main/java/seedu/address/model/appointment/exceptions/DuplicateAppointmentException.java new file mode 100644 index 00000000000..bbbaa46ea89 --- /dev/null +++ b/src/main/java/seedu/address/model/appointment/exceptions/DuplicateAppointmentException.java @@ -0,0 +1,11 @@ +package seedu.address.model.appointment.exceptions; + +/** + * Signals that the operation will result in duplicate Appointments (Appointments are considered + * duplicates if they have the same identity). + */ +public class DuplicateAppointmentException extends RuntimeException { + public DuplicateAppointmentException() { + super("Operation would result in duplicate Appointments"); + } +} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java deleted file mode 100644 index 469a2cc9a1e..00000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,65 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's address in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} - */ -public class Address { - - public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank"; - - /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. - */ - public static final String VALIDATION_REGEX = "[^\\s].*"; - - public final String value; - - /** - * Constructs an {@code Address}. - * - * @param address A valid address. - */ - public Address(String address) { - requireNonNull(address); - checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS); - value = address; - } - - /** - * Returns true if a given string is a valid email. - */ - public static boolean isValidAddress(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof Address)) { - return false; - } - - Address otherAddress = (Address) other; - return value.equals(otherAddress.value); - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Age.java b/src/main/java/seedu/address/model/person/Age.java new file mode 100644 index 00000000000..8bee93180f3 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Age.java @@ -0,0 +1,59 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Patient's age in the MedBook. + * Guarantees: immutable; is valid as declared in {@link #isValidAge(Integer)} + */ +public class Age { + public static final String MESSAGE_CONSTRAINTS = + "Age should only be a non-negative integer"; + + public final Integer age; + + /** + * Constructs a {@code Age}. + * + * @param age A valid age. + */ + public Age(Integer age) { + requireNonNull(age); + checkArgument(isValidAge(age), MESSAGE_CONSTRAINTS); + this.age = age; + } + + /** + * Returns true if a given integer is a valid age. + */ + public static boolean isValidAge(Integer test) { + return test >= 0; + } + + @Override + public String toString() { + return String.valueOf(this.age); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Age)) { + return false; + } + + Age otherAge = (Age) other; + return this.age == otherAge.age; + } + + @Override + public int hashCode() { + return this.age.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/Allergy.java b/src/main/java/seedu/address/model/person/Allergy.java new file mode 100644 index 00000000000..708d6ab1387 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Allergy.java @@ -0,0 +1,64 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents an allergy in the address book. + * Guarantees: immutable; name is valid as declared in {@link #isValidAllergy(String)} + */ +public class Allergy { + + public static final String MESSAGE_CONSTRAINTS = "Allergy names should be alphanumeric"; + + public static final String VALIDATION_REGEX = "\\p{Alnum}+"; + + public final String allergy; + + /** + * Constructs a {@code Allergy}. + * + * @param allergy A valid allergy name. + */ + + public Allergy(String allergy) { + requireNonNull(allergy); + checkArgument(isValidAllergy(allergy), MESSAGE_CONSTRAINTS); + this.allergy = allergy; + } + + /** + * Returns true if a given string is a valid tag name. + */ + public static boolean isValidAllergy(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Allergy)) { + return false; + } + + Allergy otherAllergy = (Allergy) other; + return allergy.equals(otherAllergy.allergy); + } + + @Override + public int hashCode() { + return allergy.hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + return '[' + allergy + ']'; + } + +} diff --git a/src/main/java/seedu/address/model/person/BloodType.java b/src/main/java/seedu/address/model/person/BloodType.java new file mode 100644 index 00000000000..eee84072c0e --- /dev/null +++ b/src/main/java/seedu/address/model/person/BloodType.java @@ -0,0 +1,69 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.util.Objects; + +/** + * Represents a Patient's blood type in the MedBook. + * Guarantees: immutable; is valid as declared in + * {@link #isValidBloodType(String)} + */ +public class BloodType { + public static final String + MESSAGE_CONSTRAINTS = "Blood Type should only be one of the following: A+, A-, B+, B-, AB+, AB-, O+, O-"; + + public static final String[] POSSIBLE_BLOOD_TYPES = { "A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-" }; + + public final String bloodType; + + /** + * Constructs a {@code Gender}. + * + * @param bloodType A valid blood type. + */ + public BloodType(String bloodType) { + requireNonNull(bloodType); + checkArgument(isValidBloodType(bloodType), MESSAGE_CONSTRAINTS); + this.bloodType = bloodType; + } + + /** + * Returns true if a given string is a valid gender. + */ + public static boolean isValidBloodType(String test) { + for (int i = 0; i < POSSIBLE_BLOOD_TYPES.length; i++) { + if (test.equals(POSSIBLE_BLOOD_TYPES[i])) { + return true; + } + } + + return false; + } + + @Override + public String toString() { + return this.bloodType; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof BloodType)) { + return false; + } + + BloodType otherBloodType = (BloodType) other; + return Objects.equals(this.bloodType, otherBloodType.bloodType); + } + + @Override + public int hashCode() { + return this.bloodType.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/Gender.java b/src/main/java/seedu/address/model/person/Gender.java new file mode 100644 index 00000000000..813a4cb79af --- /dev/null +++ b/src/main/java/seedu/address/model/person/Gender.java @@ -0,0 +1,62 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Patient's gender in the MedBook. + * Guarantees: immutable; is valid as declared in {@link #isValidGender(String)} + */ +public class Gender { + + public static final String MESSAGE_CONSTRAINTS = + "Gender should only be either M or F"; + + public final String gender; + + /** + * Constructs a {@code Gender}. + * + * @param gender A valid gender. + */ + public Gender(String gender) { + requireNonNull(gender); + checkArgument(isValidGender(gender), MESSAGE_CONSTRAINTS); + this.gender = gender; + } + + /** + * Returns true if a given string is a valid gender. + */ + public static boolean isValidGender(String test) { + return test.equals("M") || test.equals("F"); + } + + @Override + public String toString() { + return this.gender; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Gender)) { + return false; + } + + Gender otherGender = (Gender) other; + return this.gender.equals(otherGender.gender); + } + + @Override + public int hashCode() { + return this.gender.hashCode(); + } + + + +} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java index 62d19be2977..3642a62b06e 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java @@ -19,7 +19,22 @@ public NameContainsKeywordsPredicate(List keywords) { @Override public boolean test(Person person) { return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword) + || + StringUtil.containsWordIgnoreCase(person.getNric().nric, keyword) + || + StringUtil.containsWordIgnoreCase(person.getPhone().value, keyword) + || + person.getAllergies().stream() + .anyMatch(hobby -> StringUtil.containsWordIgnoreCase(hobby.allergy, keyword)) + || + StringUtil.containsWordIgnoreCase(person.getGender().gender, keyword) + || + StringUtil.containsWordIgnoreCase(person.getBloodType().bloodType, keyword) + || + StringUtil.containsWordIgnoreCase(person.getAge().age.toString(), keyword) + || + StringUtil.containsWordIgnoreCase(person.getEmail().value, keyword)); } @Override diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index abe8c46b535..2557d5dbb2a 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -1,5 +1,6 @@ package seedu.address.model.person; +import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.util.Collections; @@ -8,39 +9,61 @@ import java.util.Set; import seedu.address.commons.util.ToStringBuilder; -import seedu.address.model.tag.Tag; +import seedu.address.model.appointment.Appointment; +import seedu.address.model.appointment.UniqueAppointmentList; +import seedu.address.model.record.Record; +import seedu.address.model.record.UniqueRecordList; +import seedu.address.model.shared.Name; +import seedu.address.model.shared.Nric; /** * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated, immutable. + * Guarantees: details are present and not null, field values are validated, + * immutable. */ public class Person { // Identity fields private final Name name; + private final Nric nric; private final Phone phone; private final Email email; - - // Data fields - private final Address address; - private final Set tags = new HashSet<>(); + private final Gender gender; + private final Age age; + private final BloodType bloodType; + private final Set allergies = new HashSet<>(); + private final UniqueRecordList records = new UniqueRecordList(); + private final UniqueAppointmentList appointments = new UniqueAppointmentList(); + private boolean isPinned; /** - * Every field must be present and not null. + * Constructs a Person */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); + public Person(Name name, Nric nric, Email email, Phone phone, Gender gender, Age age, + BloodType bloodType, Set allergies, UniqueRecordList records, UniqueAppointmentList appointments, + boolean isPinned) { + requireAllNonNull(name, phone, email, gender, age, allergies, appointments, isPinned); this.name = name; - this.phone = phone; + this.nric = nric; this.email = email; - this.address = address; - this.tags.addAll(tags); + this.phone = phone; + this.gender = gender; + this.age = age; + this.bloodType = bloodType; + this.allergies.addAll(allergies); + this.records.setRecords(records); + this.appointments.setAppointments(appointments); + this.isPinned = isPinned; } public Name getName() { return name; } + public Nric getNric() { + return nric; + } + public Phone getPhone() { return phone; } @@ -49,20 +72,45 @@ public Email getEmail() { return email; } - public Address getAddress() { - return address; + public Gender getGender() { + return gender; + } + + public boolean isPinned() { + return isPinned; + } + + public Age getAge() { + return age; + } + + public BloodType getBloodType() { + return bloodType; + } + + public Set getAllergies() { + return Collections.unmodifiableSet(allergies); + } + + public UniqueRecordList getRecords() { + return this.records; + } + + public UniqueAppointmentList getAppointments() { + return this.appointments; } /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. + * Returns true if the same appointment as {@code appointment} exists in the + * person. */ - public Set getTags() { - return Collections.unmodifiableSet(tags); + public boolean hasAppointment(Appointment appointment) { + requireNonNull(appointment); + return appointments.contains(appointment); } /** - * Returns true if both persons have the same name. + * Returns true if both persons have the same nric. * This defines a weaker notion of equality between two persons. */ public boolean isSamePerson(Person otherPerson) { @@ -71,7 +119,15 @@ public boolean isSamePerson(Person otherPerson) { } return otherPerson != null - && otherPerson.getName().equals(getName()); + && otherPerson.getNric().equals(getNric()); + } + + /** + * Adds a record to the patient + */ + public void addRecord(Record record) { + requireNonNull(record); + this.records.add(record); } /** @@ -91,27 +147,38 @@ public boolean equals(Object other) { Person otherPerson = (Person) other; return name.equals(otherPerson.name) + && nric.equals(otherPerson.nric) && phone.equals(otherPerson.phone) && email.equals(otherPerson.email) - && address.equals(otherPerson.address) - && tags.equals(otherPerson.tags); + && gender.equals(otherPerson.gender) + && age.equals(otherPerson.age) + && bloodType.equals(otherPerson.bloodType) + && allergies.equals(otherPerson.allergies) + && records.equals(otherPerson.records) + && appointments.equals(otherPerson.appointments) + && isPinned == otherPerson.isPinned; } @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); + return Objects.hash(name, email, phone, gender, age, bloodType, allergies); } @Override public String toString() { return new ToStringBuilder(this) .add("name", name) - .add("phone", phone) + .add("nric", nric) .add("email", email) - .add("address", address) - .add("tags", tags) + .add("phone", phone) + .add("gender", gender) + .add("age", age) + .add("bloodType", bloodType) + .add("allergies", allergies) + .add("records", records) + .add("appointments", appointments) + .add("isPinned", isPinned) .toString(); } - } diff --git a/src/main/java/seedu/address/model/record/Condition.java b/src/main/java/seedu/address/model/record/Condition.java new file mode 100644 index 00000000000..350d4ceebba --- /dev/null +++ b/src/main/java/seedu/address/model/record/Condition.java @@ -0,0 +1,57 @@ +package seedu.address.model.record; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a condition of a patient. Recorded inside a Record object. + */ +public class Condition { + + public static final String MESSAGE_CONSTRAINTS = "Conditions should only contain alphanumeric characters, " + + "dashes and spaces"; + + public static final String VALIDATION_REGEX = "[a-zA-Z0-9- ]+"; + + public final String condition; + + /** + * Constructs a {@code Condition}. + * + * @param condition A valid condition name. + */ + public Condition(String condition) { + requireNonNull(condition); + checkArgument(isValidCondition(condition), MESSAGE_CONSTRAINTS); + this.condition = condition; + } + + public static boolean isValidCondition(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Condition)) { + return false; + } + + Condition otherCondition = (Condition) other; + return condition.equals(otherCondition.condition); + } + + @Override + public int hashCode() { + return condition.hashCode(); + } + + @Override + public String toString() { + return condition; + } +} diff --git a/src/main/java/seedu/address/model/record/Medication.java b/src/main/java/seedu/address/model/record/Medication.java new file mode 100644 index 00000000000..49e4be27115 --- /dev/null +++ b/src/main/java/seedu/address/model/record/Medication.java @@ -0,0 +1,55 @@ +package seedu.address.model.record; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a medication that a patient receives. Recorded inside a Record + * object. + */ +public class Medication { + public static final String MESSAGE_CONSTRAINTS = "Medications should only contain alphanumeric characters, " + + "dashes and spaces"; + + public static final String VALIDATION_REGEX = "[a-zA-Z0-9- ]+"; + public final String medication; + + /** + * Constructs a {@code Medication} + * + * @param medication A valid medication name. + */ + public Medication(String medication) { + requireNonNull(medication); + checkArgument(isValidMedication(medication), MESSAGE_CONSTRAINTS); + this.medication = medication; + } + + public static boolean isValidMedication(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (!(other instanceof Medication)) { + return false; + } + + Medication otherMedication = (Medication) other; + return this.medication.equals(otherMedication.medication); + } + + @Override + public int hashCode() { + return medication.hashCode(); + } + + @Override + public String toString() { + return medication; + } +} diff --git a/src/main/java/seedu/address/model/record/Record.java b/src/main/java/seedu/address/model/record/Record.java new file mode 100644 index 00000000000..f4ec214ed5a --- /dev/null +++ b/src/main/java/seedu/address/model/record/Record.java @@ -0,0 +1,115 @@ +package seedu.address.model.record; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.EditRecordCommand; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.shared.DateTime; +import seedu.address.storage.Storage; +import seedu.address.storage.StorageManager; + +/** + * Record of condition of a patient and date and time in which a patient visits + * the doctor + */ +public class Record { + + private final List conditions = new ArrayList<>(); + private final List medications = new ArrayList<>(); + private final DateTime dateTime; + private Path filePath; + private final Integer personIndex; + + /** + * Constructs a record object + */ + public Record(DateTime dateTime, List conditions, List medications, Path filePath, + Integer personIndex) { + requireAllNonNull(dateTime, conditions); + this.dateTime = dateTime; + this.conditions.addAll(conditions); + this.medications.addAll(medications); + this.filePath = filePath; + this.personIndex = personIndex; + } + + public DateTime getDateTime() { + return dateTime; + } + + public int getPersonIndex() { + return personIndex; + } + + public List getConditions() { + return Collections.unmodifiableList(conditions); + } + + public Path getFilePath() { + return filePath; // Getter for the file path + } + + // Set the filePath. This is the new setter method + public void setFilePath(Path filePath, int displayedIndex) { + requireAllNonNull(filePath); // Ensure the provided filePath is not null + this.filePath = filePath; + Model model = ModelManager.getInstance(); + EditRecordCommand.EditRecordDescriptor editRecordDescriptor = new EditRecordCommand.EditRecordDescriptor(); + editRecordDescriptor.setFilePath(filePath); + try { + Storage storage = StorageManager.getInstance(); + storage.saveAddressBook(model.getAddressBook()); + } catch (Exception c) { + c.printStackTrace(); + } + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(dateTime, conditions, filePath, personIndex); + } + + public List getMedications() { + return Collections.unmodifiableList(medications); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Record)) { + return false; + } + + Record otherRecord = (Record) other; + return dateTime.equals(otherRecord.dateTime) + && conditions.equals(otherRecord.conditions) + && Objects.equals(filePath, otherRecord.filePath) + && personIndex.equals(otherRecord.personIndex) + && medications.equals(otherRecord.medications); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("dateTime", dateTime) + .add("conditions", conditions) + .add("medications", medications) + .add("filePath", filePath) + .add("personIndex", personIndex) + .toString(); + } + +} diff --git a/src/main/java/seedu/address/model/record/RecordContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/record/RecordContainsKeywordsPredicate.java new file mode 100644 index 00000000000..f92e1cd57e2 --- /dev/null +++ b/src/main/java/seedu/address/model/record/RecordContainsKeywordsPredicate.java @@ -0,0 +1,46 @@ +package seedu.address.model.record; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.commons.util.ToStringBuilder; + +/** + * Tests that a {@code Record} matches any of the keywords given. + */ +public class RecordContainsKeywordsPredicate implements Predicate { + private final List keywords; + public RecordContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + @Override + public boolean test(Record record) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(record.getDateTime().toString(), keyword) + || + record.getConditions().stream() + .anyMatch(condition -> StringUtil.containsWordIgnoreCase(condition.condition, keyword)) + || + record.getMedications().stream() + .anyMatch(medication -> StringUtil.containsWordIgnoreCase(medication.medication, + keyword))); + } + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof RecordContainsKeywordsPredicate)) { + return false; + } + RecordContainsKeywordsPredicate otherRecordContainsKeywordsPredicate = (RecordContainsKeywordsPredicate) other; + return keywords.equals(otherRecordContainsKeywordsPredicate.keywords); + } + @Override + public String toString() { + return new ToStringBuilder(this) + .add("keywords", keywords) + .toString(); + } +} diff --git a/src/main/java/seedu/address/model/record/UniqueRecordList.java b/src/main/java/seedu/address/model/record/UniqueRecordList.java new file mode 100644 index 00000000000..84d9879c254 --- /dev/null +++ b/src/main/java/seedu/address/model/record/UniqueRecordList.java @@ -0,0 +1,163 @@ +package seedu.address.model.record; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.record.exceptions.DuplicateRecordException; +import seedu.address.model.record.exceptions.RecordNotFoundException; + +/** + * A list of records that enforces uniqueness between its elements and does not + * allow nulls. + * A record is considered unique by comparing using + * {@code Record#equals(Object)}. As such, adding, updating, removal of + * records uses Record#equals(Object) for equality so as to ensure that the + * record being added, updated, removed is + * unique in terms of identity in the UniqueRecordList. + * + * Supports a minimal set of list operations. + * + * @see Record#equals(Object) + */ +public class UniqueRecordList implements Iterable { + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = FXCollections + .unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent record as the given argument. + */ + public boolean contains(Record toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::equals); + } + + /** + * Returns a record at a specified index. + */ + public Record get(int index) { + return internalList.get(index); + } + + /** + * Returns the size of the internalList + */ + public int size() { + return internalList.size(); + } + + /** + * Adds a record to the list + */ + public void add(Record recordToAdd) { + requireNonNull(recordToAdd); + if (contains(recordToAdd)) { + throw new DuplicateRecordException(); + } + internalList.add(recordToAdd); + } + + /** + * Replaces the record {@code target} in the list with {@code editedRecord}. + * {@code target} must exist in the list. + * The record identity of {@code editedRecord} must not be the same as another + * existing record in the list. + */ + + public void setRecord(Record target, Record editedRecord) { + requireAllNonNull(target, editedRecord); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new RecordNotFoundException(); + } + + if (!target.equals(editedRecord) && contains(editedRecord)) { + throw new DuplicateRecordException(); + } + + internalList.set(index, editedRecord); + } + + /** + * Removes the equivalent record from the list. + * The record must exist in the list. + */ + public void remove(Record recordToRemove) { + requireNonNull(recordToRemove); + if (!internalList.remove(recordToRemove)) { + throw new RecordNotFoundException(); + } + } + + public void setRecords(UniqueRecordList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code persons}. + * {@code persons} must not contain duplicate persons. + */ + public void setRecords(List records) { + requireAllNonNull(records); + if (!recordsAreUnique(records)) { + throw new DuplicateRecordException(); + } + + internalList.setAll(records); + } + + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UniqueRecordList)) { + return false; + } + + UniqueRecordList otherUniqueRecordList = (UniqueRecordList) other; + return internalList.equals(otherUniqueRecordList.internalList); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + @Override + public String toString() { + return internalList.toString(); + } + + /** + * Returns true if {@code records} contains only unique records. + */ + private boolean recordsAreUnique(List records) { + for (int i = 0; i < records.size() - 1; i++) { + for (int j = i + 1; j < records.size(); j++) { + if (records.get(i).equals(records.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/record/exceptions/DuplicateRecordException.java b/src/main/java/seedu/address/model/record/exceptions/DuplicateRecordException.java new file mode 100644 index 00000000000..87357dc3441 --- /dev/null +++ b/src/main/java/seedu/address/model/record/exceptions/DuplicateRecordException.java @@ -0,0 +1,13 @@ +package seedu.address.model.record.exceptions; + +/** + * Signals that the operation will result in duplicate Records (Records are considered duplicates if they have the same + * DateTime and Conditions). + */ + +public class DuplicateRecordException extends RuntimeException { + public DuplicateRecordException() { + super("Operation would result in duplicate records"); + } + +} diff --git a/src/main/java/seedu/address/model/record/exceptions/RecordNotFoundException.java b/src/main/java/seedu/address/model/record/exceptions/RecordNotFoundException.java new file mode 100644 index 00000000000..12a4a74b67d --- /dev/null +++ b/src/main/java/seedu/address/model/record/exceptions/RecordNotFoundException.java @@ -0,0 +1,7 @@ +package seedu.address.model.record.exceptions; + +/** + * Signals that the operation is unable to find the specified record. + */ +public class RecordNotFoundException extends RuntimeException{ +} diff --git a/src/main/java/seedu/address/model/shared/DateTime.java b/src/main/java/seedu/address/model/shared/DateTime.java new file mode 100644 index 00000000000..d72159643b8 --- /dev/null +++ b/src/main/java/seedu/address/model/shared/DateTime.java @@ -0,0 +1,68 @@ +package seedu.address.model.shared; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.format.ResolverStyle; + +/** + * Represents the date and time in which patient visits the doctor + */ +public class DateTime { + public static final String MESSAGE_CONSTRAINTS = "DateTime should be in the format of 'dd-MM-yyyy HHmm'"; + + public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd-MM-uuuu HHmm") + .withResolverStyle(ResolverStyle.STRICT); + public final LocalDateTime dateTime; + + /** + * Constructs a {@code DateTime}. + * + * @param dateTime A valid date and time. + */ + public DateTime(String dateTime) { + requireNonNull(dateTime); + checkArgument(isValidDateTime(dateTime), MESSAGE_CONSTRAINTS); + this.dateTime = LocalDateTime.parse(dateTime, FORMATTER); + } + + /** + * Returns true if a given string is a valid date-time. + */ + public static boolean isValidDateTime(String test) { + try { + LocalDateTime parsedDateTime = LocalDateTime.parse(test, FORMATTER); + return parsedDateTime.getYear() > 0; + } catch (DateTimeParseException e) { + return false; + } + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DateTime)) { + return false; + } + + DateTime otherDateTime = (DateTime) other; + return dateTime.equals(otherDateTime.dateTime); + } + + @Override + public int hashCode() { + return dateTime.hashCode(); + } + + @Override + public String toString() { + return dateTime.format(FORMATTER); + } +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/shared/Name.java similarity index 85% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/seedu/address/model/shared/Name.java index 173f15b9b00..3381e507859 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/shared/Name.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model.shared; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; @@ -10,13 +10,13 @@ public class Name { public static final String MESSAGE_CONSTRAINTS = - "Names should only contain alphanumeric characters and spaces, and it should not be blank"; + "Names should only contain alphanumeric characters, spaces, dots, and dashes, and it should not be blank"; /* - * The first character of the address must not be a whitespace, + * The first character of the name must not be a whitespace, * otherwise " " (a blank string) becomes a valid input. */ - public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} .-]*"; public final String fullName; @@ -38,7 +38,6 @@ public static boolean isValidName(String test) { return test.matches(VALIDATION_REGEX); } - @Override public String toString() { return fullName; diff --git a/src/main/java/seedu/address/model/shared/Nric.java b/src/main/java/seedu/address/model/shared/Nric.java new file mode 100644 index 00000000000..fa78a794cbf --- /dev/null +++ b/src/main/java/seedu/address/model/shared/Nric.java @@ -0,0 +1,67 @@ +package seedu.address.model.shared; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's Nric in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidNric(String)} + */ +public class Nric { + + public static final String MESSAGE_CONSTRAINTS = "NRIC should follow the format: a letter, " + + "followed by seven digits, and then another letter"; + + /* + * The Nric starts with a letter (either uppercase or lowercase), + * followed by seven digits, and ends with another letter (either uppercase or + * lowercase). + */ + public static final String VALIDATION_REGEX = "[A-Za-z]\\d{7}[A-Za-z]"; + + public final String nric; + + /** + * Constructs a {@code Nric}. + * + * @param nric A valid Nric. + */ + public Nric(String nric) { + requireNonNull(nric); + checkArgument(isValidNric(nric), MESSAGE_CONSTRAINTS); + this.nric = nric; + } + + /** + * Returns true if a given string is a valid Nric. + */ + public static boolean isValidNric(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return nric; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Nric)) { + return false; + } + + Nric otherNric = (Nric) other; + return nric.equals(otherNric.nric); + } + + @Override + public int hashCode() { + return nric.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java deleted file mode 100644 index f1a0d4e233b..00000000000 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ /dev/null @@ -1,62 +0,0 @@ -package seedu.address.model.tag; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Tag in the address book. - * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} - */ -public class Tag { - - public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric"; - public static final String VALIDATION_REGEX = "\\p{Alnum}+"; - - public final String tagName; - - /** - * Constructs a {@code Tag}. - * - * @param tagName A valid tag name. - */ - public Tag(String tagName) { - requireNonNull(tagName); - checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS); - this.tagName = tagName; - } - - /** - * Returns true if a given string is a valid tag name. - */ - public static boolean isValidTagName(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof Tag)) { - return false; - } - - Tag otherTag = (Tag) other; - return tagName.equals(otherTag.tagName); - } - - @Override - public int hashCode() { - return tagName.hashCode(); - } - - /** - * Format state as text for viewing. - */ - public String toString() { - return '[' + tagName + ']'; - } - -} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..912dfe6b08f 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,17 +1,28 @@ package seedu.address.model.util; import java.util.Arrays; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; +import seedu.address.model.appointment.Appointment; +import seedu.address.model.appointment.UniqueAppointmentList; +import seedu.address.model.person.Age; +import seedu.address.model.person.Allergy; +import seedu.address.model.person.BloodType; import seedu.address.model.person.Email; -import seedu.address.model.person.Name; +import seedu.address.model.person.Gender; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.record.Condition; +import seedu.address.model.record.Medication; +import seedu.address.model.record.Record; +import seedu.address.model.record.UniqueRecordList; +import seedu.address.model.shared.DateTime; +import seedu.address.model.shared.Name; +import seedu.address.model.shared.Nric; /** * Contains utility methods for populating {@code AddressBook} with sample data. @@ -19,24 +30,42 @@ public class SampleDataUtil { public static Person[] getSamplePersons() { return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) + new Person(new Name("Alex Yeoh"), new Nric("S1234569A"), + new Email("alexyeoh@example.com"), new Phone("87438807"), + new Gender("M"), new Age(12), new BloodType("A+"), + getAllergySet("Peanuts"), + getRecordList(new Record(new DateTime("01-01-2001 1200"), + getConditionList("Fever"), + getMedicationList("Tylenol"), null, 0)), + getAppointmentList(new Appointment(new Name("Eye Exam"), + new DateTime("01-01-2001 1200"), + new Nric("S1234569A"))), + true), + new Person(new Name("Bernice Yu"), new Nric("T1234567A"), + new Email("berniceyu@example.com"), new Phone("99272758"), + new Gender("F"), new Age(31), new BloodType("B+"), + getAllergySet("Dust", "Peanuts"), + new UniqueRecordList(), new UniqueAppointmentList(), false), + new Person(new Name("Charlotte Oliveiro"), new Nric("S7654321A"), + new Email("charlotte@example.com"), new Phone("93210283"), + new Gender("F"), new Age(12), new BloodType("AB+"), + getAllergySet("Dust"), + new UniqueRecordList(), new UniqueAppointmentList(), false), + new Person(new Name("David Li"), new Nric("T1234567C"), + new Email("lidavid@example.com"), new Phone("91031282"), + new Gender("M"), new Age(33), new BloodType("O-"), + getAllergySet("Pollen"), new UniqueRecordList(), + new UniqueAppointmentList(), false), + new Person(new Name("Irfan Ibrahim"), new Nric("S5671234A"), + new Email("irfan@example.com"), new Phone("92492021"), + new Gender("M"), new Age(21), new BloodType("B-"), + getAllergySet("Fur"), new UniqueRecordList(), + new UniqueAppointmentList(), false), + new Person(new Name("Roy Balakrishnan"), new Nric("T1234567Z"), + new Email("royb@example.com"), new Phone("92624417"), + new Gender("M"), new Age(24), new BloodType("B+"), + getAllergySet("Grass"), new UniqueRecordList(), + new UniqueAppointmentList(), false) }; } @@ -45,16 +74,48 @@ public static ReadOnlyAddressBook getSampleAddressBook() { for (Person samplePerson : getSamplePersons()) { sampleAb.addPerson(samplePerson); } + // for (Appointment sampleAppointment : getSampleAppointments()) { + // sampleAb.addAppointment(sampleAppointment); + // } return sampleAb; } /** - * Returns a tag set containing the list of strings given. + * Returns an allergy set containing the list of strings given. */ - public static Set getTagSet(String... strings) { + public static Set getAllergySet(String... strings) { return Arrays.stream(strings) - .map(Tag::new) + .map(Allergy::new) .collect(Collectors.toSet()); } + /** + * Returns a list of conditions containing the list of strings given. + */ + public static List getConditionList(String... strings) { + return Arrays.stream(strings) + .map(Condition::new) + .collect(Collectors.toList()); + } + + /** + * Returns a list of medications containing the list of strings given. + */ + public static List getMedicationList(String... strings) { + return Arrays.stream(strings) + .map(Medication::new) + .collect(Collectors.toList()); + } + + public static UniqueRecordList getRecordList(Record... records) { + UniqueRecordList recordList = new UniqueRecordList(); + recordList.setRecords(Arrays.asList(records)); + return recordList; + } + + public static UniqueAppointmentList getAppointmentList(Appointment... appointments) { + UniqueAppointmentList appointmentList = new UniqueAppointmentList(); + appointmentList.setAppointments(Arrays.asList(appointments)); + return appointmentList; + } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedAllergy.java b/src/main/java/seedu/address/storage/JsonAdaptedAllergy.java new file mode 100644 index 00000000000..d55530973e4 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedAllergy.java @@ -0,0 +1,51 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.person.Allergy; + +/** + * Jackson-friendly version of {@link Allergy}. + */ +public class JsonAdaptedAllergy { + private final String allergy; + + /** + * Constructs a {@code JsonAdaptedAllergy} with the given {@code allergy}. + */ + + @JsonCreator + public JsonAdaptedAllergy(String allergy) { + this.allergy = allergy; + } + + /** + * Converts a given {@code Tag} into this class for Jackson use. + */ + public JsonAdaptedAllergy(Allergy source) { + this.allergy = source.allergy; + } + + @JsonValue + public String getAllergy() { + return allergy; + } + + /** + * Converts this Jackson-friendly adapted tag object into the model's {@code Allergy} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted allergy. + */ + + public Allergy toModelType() throws IllegalValueException { + if (allergy == null) { + throw new IllegalValueException(Allergy.MESSAGE_CONSTRAINTS); + } + if (!Allergy.isValidAllergy(allergy)) { + throw new IllegalValueException(Allergy.MESSAGE_CONSTRAINTS); + } + return new Allergy(allergy); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedAppointment.java b/src/main/java/seedu/address/storage/JsonAdaptedAppointment.java new file mode 100644 index 00000000000..8491d2f0f4e --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedAppointment.java @@ -0,0 +1,79 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.appointment.Appointment; +import seedu.address.model.person.Person; +import seedu.address.model.shared.DateTime; +import seedu.address.model.shared.Name; +import seedu.address.model.shared.Nric; + +/** + * Jackson-friendly version of {@link Person}. + */ +class JsonAdaptedAppointment { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Appointment's %s field is missing!"; + + private final String name; + private final String dateTime; + private final String nric; + + /** + * Constructs a {@code JsonAdaptedAppointment} with the given person details. + */ + @JsonCreator + public JsonAdaptedAppointment(@JsonProperty("name") String name, + @JsonProperty("dateTime") String dateTime, @JsonProperty("nric") String nric) { + this.name = name; + this.dateTime = dateTime; + this.nric = nric; + } + + /** + * Converts a given {@code Appointment} into this class for Jackson use. + */ + public JsonAdaptedAppointment(Appointment source) { + name = source.getName().fullName; + dateTime = source.getDateTime().toString(); + nric = source.getNric().toString(); + } + + /** + * Converts this Jackson-friendly adapted appointment object into the model's + * {@code Appointment} object. + * + * @throws IllegalValueException if there were any data constraints violated in + * the adapted appointment. + */ + public Appointment toModelType() throws IllegalValueException { + if (name == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!Name.isValidName(name)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + final Name modelname = new Name(name); + + if (dateTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + DateTime.class.getSimpleName())); + } + if (!DateTime.isValidDateTime(dateTime)) { + throw new IllegalValueException(DateTime.MESSAGE_CONSTRAINTS); + } + final DateTime modelDateTime = new DateTime(dateTime); + + if (nric == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Nric.class.getSimpleName())); + } + if (!Nric.isValidNric(nric)) { + throw new IllegalValueException(Nric.MESSAGE_CONSTRAINTS); + } + final Nric modelNric = new Nric(nric); + + return new Appointment(modelname, modelDateTime, modelNric); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedCondition.java b/src/main/java/seedu/address/storage/JsonAdaptedCondition.java new file mode 100644 index 00000000000..34ea7c156b2 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedCondition.java @@ -0,0 +1,48 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.record.Condition; + +/** + * Jackson-friendly version of {@link Condition}. + */ +public class JsonAdaptedCondition { + + private final String condition; + + /** + * Constructs a {@code JsonAdaptedCondition} with the given {@code condition}. + */ + @JsonCreator + public JsonAdaptedCondition(String condition) { + this.condition = condition; + } + /** + * Converts a given {@code Condition} into this class for Jackson use. + */ + public JsonAdaptedCondition(Condition source) { + this.condition = source.condition; + } + + @JsonValue + public String getCondition() { + return condition; + } + /** + * Converts this Jackson-friendly adapted tag object into the model's {@code Condition} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted condition. + */ + public Condition toModelType() throws IllegalValueException { + if (condition == null) { + throw new IllegalValueException(Condition.MESSAGE_CONSTRAINTS); + } + if (!Condition.isValidCondition(condition)) { + throw new IllegalValueException(Condition.MESSAGE_CONSTRAINTS); + } + return new Condition(condition); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedMedication.java b/src/main/java/seedu/address/storage/JsonAdaptedMedication.java new file mode 100644 index 00000000000..584eb2061b0 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedMedication.java @@ -0,0 +1,46 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.record.Medication; + +/** + * Jackson-friendly version of {@link Medication} + */ +public class JsonAdaptedMedication { + private final String medication; + + /** + * Constructs a {@code JsonAdaptedMedication} with the given {@code medication}. + */ + @JsonCreator + public JsonAdaptedMedication(String medication) { + this.medication = medication; + } + /** + * Converts a given {@code Medication} into this class for Jackson use. + */ + public JsonAdaptedMedication(Medication source) { + this.medication = source.medication; + } + @JsonValue + public String getMedication() { + return medication; + } + + /** + * Converts this Jackson-friendly adapted medication object into the model's {@code Medication} object. + * @throws IllegalValueException if there were any data constraints violated in the adapted condition. + */ + public Medication toModelType() throws IllegalValueException { + if (medication == null) { + throw new IllegalValueException(Medication.MESSAGE_CONSTRAINTS); + } + if (!Medication.isValidMedication(medication)) { + throw new IllegalValueException(Medication.MESSAGE_CONSTRAINTS); + } + return new Medication(medication); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java index bd1ca0f56c8..b0ac690d30b 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java @@ -10,12 +10,18 @@ import com.fasterxml.jackson.annotation.JsonProperty; import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; +import seedu.address.model.appointment.Appointment; +import seedu.address.model.appointment.UniqueAppointmentList; +import seedu.address.model.person.Age; +import seedu.address.model.person.Allergy; +import seedu.address.model.person.BloodType; import seedu.address.model.person.Email; -import seedu.address.model.person.Name; +import seedu.address.model.person.Gender; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.record.UniqueRecordList; +import seedu.address.model.shared.Name; +import seedu.address.model.shared.Nric; /** * Jackson-friendly version of {@link Person}. @@ -25,24 +31,46 @@ class JsonAdaptedPerson { public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; private final String name; + private final String nric; private final String phone; private final String email; - private final String address; - private final List tags = new ArrayList<>(); + private final String gender; + private final Integer age; + private final String bloodType; + private final List allergies = new ArrayList<>(); + private final List records = new ArrayList<>(); + private final Boolean isPinned; + private final List appointments = new ArrayList<>(); /** * Constructs a {@code JsonAdaptedPerson} with the given person details. */ @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tags") List tags) { + public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("nric") String nric, + @JsonProperty("email") String email, @JsonProperty("phone") String phone, + @JsonProperty("gender") String gender, + @JsonProperty("age") Integer age, @JsonProperty("bloodType") String bloodType, + @JsonProperty("allergies") List allergies, + @JsonProperty("records") List records, + @JsonProperty("appointments") List appointments, + @JsonProperty("isPinned") Boolean isPinned) { + this.name = name; + this.nric = nric; this.phone = phone; this.email = email; - this.address = address; - if (tags != null) { - this.tags.addAll(tags); + this.gender = gender; + this.age = age; + this.bloodType = bloodType; + if (allergies != null) { + this.allergies.addAll(allergies); + } + if (records != null) { + this.records.addAll(records); + } + this.isPinned = isPinned; + if (appointments != null) { + this.appointments.addAll(appointments); } } @@ -51,23 +79,40 @@ public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone */ public JsonAdaptedPerson(Person source) { name = source.getName().fullName; + nric = source.getNric().nric; phone = source.getPhone().value; email = source.getEmail().value; - address = source.getAddress().value; - tags.addAll(source.getTags().stream() - .map(JsonAdaptedTag::new) + gender = source.getGender().gender; + age = source.getAge().age; + bloodType = source.getBloodType().bloodType; + allergies.addAll(source.getAllergies().stream() + .map(JsonAdaptedAllergy::new) .collect(Collectors.toList())); + records.addAll(source.getRecords().asUnmodifiableObservableList() + .stream() + .map(JsonAdaptedRecord::new) + .collect(Collectors.toList())); + appointments.addAll(source.getAppointments().asUnmodifiableObservableList().stream() + .map(JsonAdaptedAppointment::new).collect(Collectors.toList())); + isPinned = source.isPinned(); } /** - * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. + * Converts this Jackson-friendly adapted person object into the model's + * {@code Person} object. * - * @throws IllegalValueException if there were any data constraints violated in the adapted person. + * @throws IllegalValueException if there were any data constraints violated in + * the adapted person. */ public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (JsonAdaptedTag tag : tags) { - personTags.add(tag.toModelType()); + final List allergiesList = new ArrayList<>(); + for (JsonAdaptedAllergy allergy : allergies) { + allergiesList.add(allergy.toModelType()); + } + + final UniqueRecordList modelRecords = new UniqueRecordList(); + for (JsonAdaptedRecord record : records) { + modelRecords.add(record.toModelType()); } if (name == null) { @@ -78,6 +123,15 @@ public Person toModelType() throws IllegalValueException { } final Name modelName = new Name(name); + if (nric == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, seedu.address.model.shared.Nric.class.getSimpleName())); + } + if (!Nric.isValidNric(nric)) { + throw new IllegalValueException(seedu.address.model.shared.Nric.MESSAGE_CONSTRAINTS); + } + final Nric modelNric = new Nric(nric); + if (phone == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); } @@ -94,16 +148,40 @@ public Person toModelType() throws IllegalValueException { } final Email modelEmail = new Email(email); - if (address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); + if (gender == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Gender.class.getSimpleName())); } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); + if (!Gender.isValidGender(gender)) { + throw new IllegalValueException(Gender.MESSAGE_CONSTRAINTS); } - final Address modelAddress = new Address(address); + final Gender modelGender = new Gender(gender); - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); - } + if (age == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Age.class.getSimpleName())); + } + if (!Age.isValidAge(age)) { + throw new IllegalValueException(Age.MESSAGE_CONSTRAINTS); + } + final Age modelAge = new Age(age); + + if (bloodType == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + BloodType.class.getSimpleName())); + } + if (!BloodType.isValidBloodType(bloodType)) { + throw new IllegalValueException(BloodType.MESSAGE_CONSTRAINTS); + } + final BloodType modelBloodType = new BloodType(bloodType); + final Set modelAllergies = new HashSet<>(allergiesList); + + final UniqueAppointmentList modelAppointments = new UniqueAppointmentList(); + for (JsonAdaptedAppointment jsonAdaptedAppointment : appointments) { + Appointment appointment = jsonAdaptedAppointment.toModelType(); + modelAppointments.add(appointment); + } + + return new Person(modelName, modelNric, modelEmail, modelPhone, modelGender, + modelAge, modelBloodType, modelAllergies, modelRecords, modelAppointments, isPinned); + } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedRecord.java b/src/main/java/seedu/address/storage/JsonAdaptedRecord.java new file mode 100644 index 00000000000..148863f02d3 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedRecord.java @@ -0,0 +1,108 @@ +package seedu.address.storage; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.record.Condition; +import seedu.address.model.record.Medication; +import seedu.address.model.record.Record; +import seedu.address.model.shared.DateTime; + +/** + * Jackson-friendly version of {@link Record}. + */ +public class JsonAdaptedRecord { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Record's %s field is missing!"; + private final String dateTime; + private final List conditions = new ArrayList<>(); + private final String filePath; + private final Integer personIndex; + private final List medications = new ArrayList<>(); + + + /** + * Constructs a {@code JsonAdoptedRecord} with the given record details. + */ + @JsonCreator + public JsonAdaptedRecord(@JsonProperty("dateTime") String dateTime, + @JsonProperty("condition") List conditions, + @JsonProperty("medication") List medications, + @JsonProperty("filePath") String filePath, + @JsonProperty("personIndex") Integer personIndex) { + this.dateTime = dateTime; + if (conditions != null) { + this.conditions.addAll(conditions); + } + this.filePath = filePath; + this.personIndex = personIndex; + if (medications != null) { + this.medications.addAll(medications); + } + } + + /** + * Converts a given {@code Record} into this class for Jackson use. + */ + public JsonAdaptedRecord(Record source) { + this.dateTime = source.getDateTime().toString(); + this.personIndex = source.getPersonIndex(); + this.filePath = source.getFilePath() == null ? null : source.getFilePath().toString(); + this.conditions.addAll(source.getConditions().stream() + .map(JsonAdaptedCondition::new) + .collect(Collectors.toList())); + this.medications.addAll(source.getMedications().stream() + .map(JsonAdaptedMedication::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted person object into the model's + * {@code Person} object. + * + * @throws IllegalValueException if there were any data constraints violated in + * the adapted person. + */ + public Record toModelType() throws IllegalValueException { + final List conditionsList = new ArrayList<>(); + final List medicationsList = new ArrayList<>(); + + for (JsonAdaptedCondition condition : conditions) { + if (condition == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Condition.class.getSimpleName())); + } + conditionsList.add(condition.toModelType()); + } + + for (JsonAdaptedMedication medication : medications) { + if (medication == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Medication.class.getSimpleName())); + } + medicationsList.add(medication.toModelType()); + } + + if (dateTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + DateTime.class.getSimpleName())); + } + + final DateTime modelDateTime = new DateTime(dateTime); + final List modelMedications = new ArrayList<>(medicationsList); + final List modelConditions = new ArrayList<>(conditionsList); + if (filePath == null) { + return new Record(modelDateTime, modelConditions, modelMedications, null, personIndex); + } else { + Path modelFilePath = Paths.get(filePath); + return new Record(modelDateTime, modelConditions, modelMedications, modelFilePath, personIndex); + } + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java deleted file mode 100644 index 0df22bdb754..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ /dev/null @@ -1,48 +0,0 @@ -package seedu.address.storage; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; - -/** - * Jackson-friendly version of {@link Tag}. - */ -class JsonAdaptedTag { - - private final String tagName; - - /** - * Constructs a {@code JsonAdaptedTag} with the given {@code tagName}. - */ - @JsonCreator - public JsonAdaptedTag(String tagName) { - this.tagName = tagName; - } - - /** - * Converts a given {@code Tag} into this class for Jackson use. - */ - public JsonAdaptedTag(Tag source) { - tagName = source.tagName; - } - - @JsonValue - public String getTagName() { - return tagName; - } - - /** - * Converts this Jackson-friendly adapted tag object into the model's {@code Tag} object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted tag. - */ - public Tag toModelType() throws IllegalValueException { - if (!Tag.isValidTagName(tagName)) { - throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS); - } - return new Tag(tagName); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java index 41e06f264e1..573f3d961cb 100644 --- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java +++ b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java @@ -3,13 +3,14 @@ import static java.util.Objects.requireNonNull; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; import java.util.Optional; import java.util.logging.Logger; import seedu.address.commons.core.LogsCenter; import seedu.address.commons.exceptions.DataLoadingException; -import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.commons.util.FileUtil; import seedu.address.commons.util.JsonUtil; import seedu.address.model.ReadOnlyAddressBook; @@ -23,58 +24,94 @@ public class JsonAddressBookStorage implements AddressBookStorage { private Path filePath; + /** + * Constructs a new JsonAddressBookStorage instance with a given filePath. + * + * @param filePath The path to the file. + */ public JsonAddressBookStorage(Path filePath) { this.filePath = filePath; } + /** + * Returns the file path where the AddressBook is stored. + * + * @return The file path. + */ public Path getAddressBookFilePath() { return filePath; } + /** + * Reads the AddressBook from the default file path. + * + * @return An Optional containing the AddressBook if it exists, or empty + * otherwise. + * @throws DataLoadingException if there was an error reading the AddressBook. + */ @Override public Optional readAddressBook() throws DataLoadingException { return readAddressBook(filePath); } /** - * Similar to {@link #readAddressBook()}. + * Reads the AddressBook from the specified file path. * - * @param filePath location of the data. Cannot be null. - * @throws DataLoadingException if loading the data from storage failed. + * @param filePath The path to the file. + * @return An Optional containing the AddressBook if it exists, or empty + * otherwise. + * @throws DataLoadingException if there was an error reading the AddressBook. */ public Optional readAddressBook(Path filePath) throws DataLoadingException { requireNonNull(filePath); - Optional jsonAddressBook = JsonUtil.readJsonFile( - filePath, JsonSerializableAddressBook.class); - if (!jsonAddressBook.isPresent()) { + if (!FileUtil.isFileExists(filePath)) { return Optional.empty(); } try { - return Optional.of(jsonAddressBook.get().toModelType()); - } catch (IllegalValueException ive) { - logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); - throw new DataLoadingException(ive); + String data = new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8); + + JsonSerializableAddressBook jsonAddressBook = JsonUtil.fromJsonString( + data, JsonSerializableAddressBook.class); + + if (jsonAddressBook == null) { + return Optional.empty(); + } + + return Optional.of(jsonAddressBook.toModelType()); + + } catch (Exception e) { + throw new DataLoadingException(e); } } + /** + * Saves the AddressBook to the default file path. + * + * @param addressBook The AddressBook to be saved. + * @throws IOException if there was an error writing to the file. + */ @Override public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { saveAddressBook(addressBook, filePath); } /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)}. + * Saves the AddressBook to the specified file path. * - * @param filePath location of the data. Cannot be null. + * @param addressBook The AddressBook to be saved. + * @param filePath The path to the file. + * @throws IOException if there was an error writing to the file. */ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { requireNonNull(addressBook); requireNonNull(filePath); FileUtil.createIfMissing(filePath); - JsonUtil.saveJsonFile(new JsonSerializableAddressBook(addressBook), filePath); + String jsonData = JsonUtil.toJsonString(new JsonSerializableAddressBook(addressBook)); + + Files.write(filePath, jsonData.getBytes(StandardCharsets.UTF_8)); } } diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index 8b84a9024d5..b907e53f93f 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -17,15 +17,18 @@ public class StorageManager implements Storage { private static final Logger logger = LogsCenter.getLogger(StorageManager.class); + private static StorageManager instance; private AddressBookStorage addressBookStorage; private UserPrefsStorage userPrefsStorage; /** - * Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}. + * Creates a {@code StorageManager} with the given {@code AddressBookStorage} + * and {@code UserPrefStorage}. */ public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { this.addressBookStorage = addressBookStorage; this.userPrefsStorage = userPrefsStorage; + this.instance = this; } // ================ UserPrefs methods ============================== @@ -45,7 +48,6 @@ public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { userPrefsStorage.saveUserPrefs(userPrefs); } - // ================ AddressBook methods ============================== @Override @@ -75,4 +77,8 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) thro addressBookStorage.saveAddressBook(addressBook, filePath); } + public static StorageManager getInstance() { + return instance; + } + } diff --git a/src/main/java/seedu/address/ui/AppointmentCalendarPanel.java b/src/main/java/seedu/address/ui/AppointmentCalendarPanel.java new file mode 100644 index 00000000000..18d35c87d92 --- /dev/null +++ b/src/main/java/seedu/address/ui/AppointmentCalendarPanel.java @@ -0,0 +1,135 @@ +package seedu.address.ui; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.appointment.Appointment; + +/** + * Panel containing the list of appointments. + */ +public class AppointmentCalendarPanel extends UiPart { + private static final String FXML = "AppointmentCalenderPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(AppointmentCalendarPanel.class); + + @FXML + private ObservableList appointmentList; + + @FXML + private GridPane calendarGrid; + @FXML + private Label currentMonthLabel; + + private LocalDate currentDate; + + /** + * Creates a {@code AppointmentCalender + * Panel} with the given {@code ObservableList}. + */ + public AppointmentCalendarPanel(ObservableList appointmentList) { + super(FXML); + this.appointmentList = appointmentList; + currentDate = LocalDate.now(); + populateMonth(currentDate); + this.appointmentList.addListener((javafx.collections.ListChangeListener.Change c) -> { + populateMonth(currentDate); + }); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Appointment} + * using + * a {@code AppointmentCard}. + */ + class AppointmentListViewCell extends ListCell { + @Override + protected void updateItem(Appointment appointment, boolean empty) { + super.updateItem(appointment, empty); + + if (empty || appointment == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new AppointmentCard(appointment, getIndex() + 1).getRoot()); + } + } + } + + @FXML + private void previousMonth() { + currentDate = currentDate.minusMonths(1); + populateMonth(currentDate); + } + + @FXML + private void nextMonth() { + currentDate = currentDate.plusMonths(1); + populateMonth(currentDate); + } + + private void populateMonth(LocalDate date) { + calendarGrid.getChildren().clear(); // Clear the previous month's data + currentMonthLabel.setText(date.getMonth() + " " + date.getYear()); + + // Print headers (days of the week) + DayOfWeek[] daysOfWeek = { DayOfWeek.SUNDAY, DayOfWeek.MONDAY, DayOfWeek.TUESDAY, DayOfWeek.WEDNESDAY, + DayOfWeek.THURSDAY, DayOfWeek.FRIDAY, DayOfWeek.SATURDAY }; + for (int i = 0; i < daysOfWeek.length; i++) { + Label label = new Label(daysOfWeek[i].toString().substring(0, 3)); + label.setAlignment(Pos.CENTER); + label.getStyleClass().add("label-bright"); + calendarGrid.add(label, i, 0); + } + + // Determine the first day of the month + LocalDate firstOfMonth = date.withDayOfMonth(1); + int dayOfWeek = firstOfMonth.getDayOfWeek().getValue() % 7; + + int rowOffset = dayOfWeek; + + // Populate days + for (int day = 1; day <= date.lengthOfMonth(); day++) { + VBox dateBox = new VBox(); + dateBox.setMinHeight(50); + Label dateLabel = new Label(Integer.toString(day)); + dateLabel.getStyleClass().add("label-bright"); + dateBox.getChildren().add(dateLabel); + int currentRow = 1 + (day + rowOffset - 1) / 7; + calendarGrid.add(dateBox, dayOfWeek, currentRow); + dayOfWeek = (dayOfWeek + 1) % 7; + } + for (Appointment appointment : appointmentList) { + LocalDate appointmentDate = appointment.getDateTime().dateTime.toLocalDate(); + if (appointmentDate.getMonth() == date.getMonth() && appointmentDate.getYear() == date.getYear()) { + // This appointment belongs to the currently displayed month + + int day = appointmentDate.getDayOfMonth(); + int cellDayOfWeek = (firstOfMonth.getDayOfWeek().getValue() + day - 1) % 7; + // Find the label that corresponds to this day + for (Node node : calendarGrid.getChildren()) { + if (GridPane.getRowIndex(node) == 1 + (day + rowOffset - 1) / 7 + && GridPane.getColumnIndex(node) == cellDayOfWeek) { + VBox dateBox = (VBox) node; + if (dateBox.getChildren().size() < 3) { // Assuming 'n' is 3 for illustration + Label appointmentLabel = new Label(appointment.getName().fullName); + appointmentLabel.getStyleClass().add("label-bright"); + dateBox.getChildren().add(appointmentLabel); + break; + } + } + } + } + } + } +} diff --git a/src/main/java/seedu/address/ui/AppointmentCard.java b/src/main/java/seedu/address/ui/AppointmentCard.java new file mode 100644 index 00000000000..c39608fd59d --- /dev/null +++ b/src/main/java/seedu/address/ui/AppointmentCard.java @@ -0,0 +1,51 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.appointment.Appointment; + +/** + * An UI component that displays information of a {@code Appointment}. + */ +public class AppointmentCard extends UiPart { + + private static final String FXML = "AppointmentListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved + * keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The + * issue on AddressBook level 4 + */ + + public final Appointment appointment; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label dateTime; + @FXML + private Label nric; + @FXML + private Label id; + + /** + * Creates a {@code AppointmentCode} with the given {@code Appointment} and + * index to display. + */ + public AppointmentCard(Appointment appointment, int displayedIndex) { + super(FXML); + this.appointment = appointment; + id.setText(displayedIndex + ". "); + name.setText(appointment.getName().fullName); + dateTime.setText(appointment.getDateTime().toString()); + nric.setText(appointment.getNric().nric); + } +} diff --git a/src/main/java/seedu/address/ui/AppointmentListPanel.java b/src/main/java/seedu/address/ui/AppointmentListPanel.java new file mode 100644 index 00000000000..19c56e2231b --- /dev/null +++ b/src/main/java/seedu/address/ui/AppointmentListPanel.java @@ -0,0 +1,50 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.appointment.Appointment; + +/** + * Panel containing the list of appointments. + */ +public class AppointmentListPanel extends UiPart { + private static final String FXML = "AppointmentListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(AppointmentListPanel.class); + + @FXML + private ListView appointmentListView; + + /** + * Creates a {@code AppointmentListPanel} with the given {@code ObservableList}. + */ + public AppointmentListPanel(ObservableList appointmentList) { + super(FXML); + appointmentListView.setItems(appointmentList); + appointmentListView.setCellFactory(listView -> new AppointmentListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Appointment} + * using + * a {@code AppointmentCard}. + */ + class AppointmentListViewCell extends ListCell { + @Override + protected void updateItem(Appointment appointment, boolean empty) { + super.updateItem(appointment, empty); + + if (empty || appointment == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new AppointmentCard(appointment, getIndex() + 1).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/AppointmentsWindow.java b/src/main/java/seedu/address/ui/AppointmentsWindow.java new file mode 100644 index 00000000000..dc71a7c2896 --- /dev/null +++ b/src/main/java/seedu/address/ui/AppointmentsWindow.java @@ -0,0 +1,103 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.layout.StackPane; +import javafx.stage.Stage; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.appointment.Appointment; + +/** + * Controller for an Appointments page + */ +public class AppointmentsWindow extends UiPart { + + private static final String FXML = "AppointmentsWindow.fxml"; + + private final Logger logger = LogsCenter.getLogger(AppointmentsWindow.class); + + private ObservableList appointmentList; + + // Independent Ui parts residing in this Ui container + private AppointmentListPanel appointmentListPanel; + private AppointmentCalendarPanel appointmentCalenderPanel; + + @FXML + private StackPane appointmentListPanelPlaceholder; + @FXML + private StackPane appointmentCalenderPanelPlaceholder; + + /** + * Creates a new AppointmentsWindow. + * + * @param root Stage to use as the root of the AppointmentsWindow. + */ + public AppointmentsWindow(Stage root, ObservableList appointmentList) { + super(FXML, root); + this.appointmentList = appointmentList; + } + + /** + * Fills up all the placeholders of this window. + */ + void fillInnerParts() { + appointmentCalenderPanelPlaceholder.getChildren().clear(); + + appointmentListPanel = new AppointmentListPanel(appointmentList); + appointmentListPanelPlaceholder.getChildren().add(appointmentListPanel.getRoot()); + + appointmentCalenderPanel = new AppointmentCalendarPanel(appointmentList); + appointmentCalenderPanelPlaceholder.getChildren().add(appointmentCalenderPanel.getRoot()); + } + + /** + * Shows the appointment window. + * + * @throws IllegalStateException + *
      + *
    • + * if this method is called on a thread other than + * the JavaFX Application Thread. + *
    • + *
    • + * if this method is called during animation or + * layout processing. + *
    • + *
    • + * if this method is called on the primary stage. + *
    • + *
    • + * if {@code dialogStage} is already showing. + *
    • + *
    + */ + public void show() { + fillInnerParts(); + logger.fine("Showing appointments window."); + getRoot().show(); + getRoot().centerOnScreen(); + } + + /** + * Returns true if the appointments window is currently being shown. + */ + public boolean isShowing() { + return getRoot().isShowing(); + } + + /** + * Hides the appointments window. + */ + public void hide() { + getRoot().hide(); + } + + /** + * Focuses on the appointments window. + */ + public void focus() { + getRoot().requestFocus(); + } +} diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 3f16b2fcf26..08759f0f4de 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -9,23 +9,73 @@ import javafx.scene.input.ClipboardContent; import javafx.stage.Stage; import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.commands.AddAppointmentCommand; +import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddRecordCommand; +import seedu.address.logic.commands.DeleteAppointmentCommand; +import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeleteRecordCommand; +import seedu.address.logic.commands.EditCommand; +import seedu.address.logic.commands.EditRecordCommand; +import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FindRecordCommand; +import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.PinCommand; +import seedu.address.logic.commands.UnpinCommand; +import seedu.address.logic.commands.ViewAppointmentCommand; +import seedu.address.logic.commands.ViewCommand; /** * Controller for a help page */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; - public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; - + public static final String USERGUIDE_URL = "https://ay2324s1-cs2103t-t12-4.github.io/tp/UserGuide.html"; + public static final String HELP_MESSAGE = "A more detailed User " + + "Guide can be accessed " + "from this url: "; + public static final String COMMAND_SUMMARY = "COMMAND SUMMARY"; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); private static final String FXML = "HelpWindow.fxml"; @FXML private Button copyButton; - + @FXML + private Label commandSummary; @FXML private Label helpMessage; + @FXML + private Label addPatient; + @FXML + private Label editPatient; + @FXML + private Label addRecord; + @FXML + private Label editRecord; + @FXML + private Label addAppointment; + @FXML + private Label viewAppointment; + @FXML + private Label deleteAppointment; + @FXML + private Label findRecords; + @FXML + private Label deleteRecord; + @FXML + private Label list; + @FXML + private Label find; + @FXML + private Label pin; + @FXML + private Label unpin; + @FXML + private Label view; + @FXML + private Label delete; + @FXML + private Label exit; /** * Creates a new HelpWindow. @@ -34,7 +84,24 @@ public class HelpWindow extends UiPart { */ public HelpWindow(Stage root) { super(FXML, root); - helpMessage.setText(HELP_MESSAGE); + commandSummary.setText(COMMAND_SUMMARY); + helpMessage.setText(HELP_MESSAGE + USERGUIDE_URL); + addPatient.setText(AddCommand.MESSAGE_USAGE); + editPatient.setText(EditCommand.MESSAGE_USAGE); + addRecord.setText(AddRecordCommand.MESSAGE_USAGE); + editRecord.setText(EditRecordCommand.MESSAGE_USAGE); + addAppointment.setText(AddAppointmentCommand.MESSAGE_USAGE); + viewAppointment.setText(ViewAppointmentCommand.MESSAGE_USAGE); + deleteAppointment.setText(DeleteAppointmentCommand.MESSAGE_USAGE); + pin.setText(PinCommand.MESSAGE_USAGE); + unpin.setText(UnpinCommand.MESSAGE_USAGE); + view.setText(ViewCommand.MESSAGE_USAGE); + delete.setText(DeleteCommand.MESSAGE_USAGE); + deleteRecord.setText(DeleteRecordCommand.MESSAGE_USAGE); + find.setText(FindCommand.MESSAGE_USAGE); + findRecords.setText(FindRecordCommand.MESSAGE_USAGE); + list.setText(ListCommand.COMMAND_WORD + ": Lists all patients in MedBook."); + exit.setText(ExitCommand.COMMAND_WORD + ": Exits the application."); } /** @@ -46,21 +113,24 @@ public HelpWindow() { /** * Shows the help window. + * * @throws IllegalStateException - *
      - *
    • - * if this method is called on a thread other than the JavaFX Application Thread. - *
    • - *
    • - * if this method is called during animation or layout processing. - *
    • - *
    • - * if this method is called on the primary stage. - *
    • - *
    • - * if {@code dialogStage} is already showing. - *
    • - *
    + *
      + *
    • + * if this method is called on a thread other than + * the JavaFX Application Thread. + *
    • + *
    • + * if this method is called during animation or + * layout processing. + *
    • + *
    • + * if this method is called on the primary stage. + *
    • + *
    • + * if {@code dialogStage} is already showing. + *
    • + *
    */ public void show() { logger.fine("Showing help page about the application."); diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 79e74ef37c0..77d93a5e2a0 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -24,6 +24,7 @@ public class MainWindow extends UiPart { private static final String FXML = "MainWindow.fxml"; + private static MainWindow mainWindow; private final Logger logger = LogsCenter.getLogger(getClass()); @@ -34,19 +35,24 @@ public class MainWindow extends UiPart { private PersonListPanel personListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; - + private AppointmentsWindow appointmentsWindow; + private PinnedPersonListPanel pinnedPersonListPanel; + private RecordListPanel recordListPanel; + private PersonListPanel personBeingViewedPanel; @FXML private StackPane commandBoxPlaceholder; - @FXML private MenuItem helpMenuItem; - @FXML private StackPane personListPanelPlaceholder; - + @FXML + private StackPane pinnedPersonListPanelPlaceholder; + @FXML + private StackPane personBeingViewedPanelPlaceholder; + @FXML + private StackPane recordListPanelPlaceholder; @FXML private StackPane resultDisplayPlaceholder; - @FXML private StackPane statusbarPlaceholder; @@ -66,6 +72,8 @@ public MainWindow(Stage primaryStage, Logic logic) { setAccelerators(); helpWindow = new HelpWindow(); + appointmentsWindow = new AppointmentsWindow(new Stage(), logic.getFilteredAppointmentList()); + mainWindow = this; } public Stage getPrimaryStage() { @@ -76,8 +84,17 @@ private void setAccelerators() { setAccelerator(helpMenuItem, KeyCombination.valueOf("F1")); } + public static MainWindow getInstance() { + return mainWindow; + } + + public void setResultDisplay(String s) { + resultDisplay.setFeedbackToUser(s); + } + /** * Sets the accelerator of a MenuItem. + * * @param keyCombination the KeyCombination value of the accelerator */ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { @@ -113,6 +130,15 @@ void fillInnerParts() { personListPanel = new PersonListPanel(logic.getFilteredPersonList()); personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + pinnedPersonListPanel = new PinnedPersonListPanel(logic.getPinnedPersonList()); + pinnedPersonListPanelPlaceholder.getChildren().add(pinnedPersonListPanel.getRoot()); + + recordListPanel = new RecordListPanel(logic.getFilteredRecordList()); + recordListPanelPlaceholder.getChildren().add(recordListPanel.getRoot()); + + personBeingViewedPanel = new PersonListPanel(logic.getPersonBeingViewed()); + personBeingViewedPanelPlaceholder.getChildren().add(personBeingViewedPanel.getRoot()); + resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); @@ -147,6 +173,18 @@ public void handleHelp() { } } + /** + * Opens the appointments window or focuses on it if it's already opened. + */ + @FXML + public void handleAppointments() { + if (!appointmentsWindow.isShowing()) { + appointmentsWindow.show(); + } else { + appointmentsWindow.focus(); + } + } + void show() { primaryStage.show(); } @@ -182,6 +220,10 @@ private CommandResult executeCommand(String commandText) throws CommandException handleHelp(); } + if (commandResult.isShowAppointments()) { + handleAppointments(); + } + if (commandResult.isExit()) { handleExit(); } diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index 094c42cda82..9d43fb33f37 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -31,15 +31,21 @@ public class PersonCard extends UiPart { @FXML private Label name; @FXML + private Label nric; + @FXML private Label id; @FXML private Label phone; @FXML - private Label address; - @FXML private Label email; @FXML - private FlowPane tags; + private Label age; + @FXML + private FlowPane allergies; + @FXML + private Label gender; + @FXML + private Label bloodType; /** * Creates a {@code PersonCode} with the given {@code Person} and index to display. @@ -49,11 +55,21 @@ public PersonCard(Person person, int displayedIndex) { this.person = person; id.setText(displayedIndex + ". "); name.setText(person.getName().fullName); + nric.setText(person.getNric().nric); phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); + age.setText(String.valueOf(person.getAge().age)); + gender.setText(person.getGender().gender); email.setText(person.getEmail().value); - person.getTags().stream() - .sorted(Comparator.comparing(tag -> tag.tagName)) - .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + bloodType.setText(person.getBloodType().bloodType); + + person.getAllergies().stream() + .sorted(Comparator.comparing(allergy -> allergy.allergy)) + .forEach(allergy -> + { + Label label = new Label(allergy.allergy + " "); + label.setWrapText(true); + label.setMaxWidth(100); + allergies.getChildren().add(label); + }); } } diff --git a/src/main/java/seedu/address/ui/PinnedPersonListPanel.java b/src/main/java/seedu/address/ui/PinnedPersonListPanel.java new file mode 100644 index 00000000000..112f1dec360 --- /dev/null +++ b/src/main/java/seedu/address/ui/PinnedPersonListPanel.java @@ -0,0 +1,49 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.person.Person; + +/** + * Panel containing the list of pinned persons. + */ +public class PinnedPersonListPanel extends UiPart { + private static final String FXML = "PinnedPersonListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(PinnedPersonListPanel.class); + + @FXML + private ListView pinnedPersonListView; + + /** + * Creates a {@code PinnedPersonListPanel} with the given {@code ObservableList}. + */ + public PinnedPersonListPanel(ObservableList pinnedPersonList) { + super(FXML); + pinnedPersonListView.setItems(pinnedPersonList); + pinnedPersonListView.setCellFactory(listView -> new PinnedPersonListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. + */ + class PinnedPersonListViewCell extends ListCell { + @Override + protected void updateItem(Person person, boolean empty) { + super.updateItem(person, empty); + + if (empty || person == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/RecordCard.java b/src/main/java/seedu/address/ui/RecordCard.java new file mode 100644 index 00000000000..909c02f35d7 --- /dev/null +++ b/src/main/java/seedu/address/ui/RecordCard.java @@ -0,0 +1,107 @@ +package seedu.address.ui; + +import java.awt.Desktop; +import java.io.File; +import java.io.IOException; + +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Hyperlink; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.stage.FileChooser; +import seedu.address.model.record.Record; + +/** + * An UI component that displays information of a {@code Record}. + */ +public class RecordCard extends UiPart { + + private static final String FXML = "RecordListCard.fxml"; + + public final Record record; + @FXML + private HBox cardPane; + @FXML + private Label id; + @FXML + private Label dateTime; + @FXML + private Label condition; + @FXML + private Hyperlink filePathLabel; + @FXML + private Label medication; + + private int displayedIndex; + private MainWindow mainWindow; + + /** + * Creates a {@code RecordCard} with the given {@code Record} and index to + * display. + * + * @param record The record object to be displayed. + * @param displayedIndex The index at which the record will be displayed. + */ + public RecordCard(Record record, int displayedIndex) { + super(FXML); + this.record = record; + id.setText(displayedIndex + ". "); + dateTime.setText(record.getDateTime().toString()); + condition.setText(record.getConditions().toString()); + if (record.getFilePath() != null) { + filePathLabel.setText(record.getFilePath().toString()); + } else { + filePathLabel.setText("No file attached"); + filePathLabel.setDisable(true); // Disable the hyperlink if no file is attached + } + this.displayedIndex = displayedIndex; + medication.setText(record.getMedications().toString()); + mainWindow = MainWindow.getInstance(); + } + + /** + * Opens a file chooser dialog to attach a file to the record. + * + * @param event The event triggering this method call. + */ + @FXML + public void handleAttachFile(ActionEvent event) { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Select File to Attach"); + File file = fileChooser.showOpenDialog(cardPane.getScene().getWindow()); + + if (file != null) { + this.record.setFilePath(file.toPath(), displayedIndex); + filePathLabel.setText(file.toPath().toString()); + mainWindow.setResultDisplay("File successfully attached!"); + } else { + mainWindow.setResultDisplay("No file chosen"); + } + + } + + /** + * Opens the file associated with the record if available. + * + * @param event The event triggering this method call. + */ + @FXML + public void handleOpenFile(ActionEvent event) { + if (record.getFilePath() != null && Desktop.isDesktopSupported()) { + try { + Desktop.getDesktop().open(record.getFilePath().toFile()); + mainWindow.setResultDisplay("File successfully opened"); + } catch (IllegalArgumentException e) { + // Handle the case where the file does not exist or is not valid. + + mainWindow.setResultDisplay("There was an issue with the file: " + e.getMessage()); + } catch (IOException e) { + // Handle other I/O errors. + e.printStackTrace(); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/RecordListPanel.java b/src/main/java/seedu/address/ui/RecordListPanel.java new file mode 100644 index 00000000000..c0eaa00811c --- /dev/null +++ b/src/main/java/seedu/address/ui/RecordListPanel.java @@ -0,0 +1,52 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.record.Record; + +/** + * Panel contains a list of records. + */ + +public class RecordListPanel extends UiPart { + + private static final String FXML = "RecordListPanel.fxml"; + + private final Logger logger = LogsCenter.getLogger(RecordListPanel.class); + + @FXML + private ListView recordListView; + + /** + * Creates a {@code RecordListPanel} with the given {@code ObservableList}. + */ + public RecordListPanel(ObservableList recordList) { + super(FXML); + recordListView.setItems(recordList); + recordListView.setCellFactory(listView -> new RecordListPanel.RecordListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Record} using + * a {@code RecordCard}. + */ + class RecordListViewCell extends ListCell { + @Override + protected void updateItem(Record record, boolean empty) { + super.updateItem(record, empty); + + if (empty || record == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new RecordCard(record, getIndex() + 1).getRoot()); + } + } + } +} diff --git a/src/main/resources/view/AppointmentCalenderPanel.fxml b/src/main/resources/view/AppointmentCalenderPanel.fxml new file mode 100644 index 00000000000..7d9937c4cd6 --- /dev/null +++ b/src/main/resources/view/AppointmentCalenderPanel.fxml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + - - - - - - - - - - - - diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 7778f666a0a..4fd5260044b 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -11,50 +11,81 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index f5e812e25e6..5e5e56d7b47 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -7,30 +7,58 @@ + - + - + - - - + + + diff --git a/src/main/resources/view/PinnedPersonListPanel.fxml b/src/main/resources/view/PinnedPersonListPanel.fxml new file mode 100644 index 00000000000..e29159ec918 --- /dev/null +++ b/src/main/resources/view/PinnedPersonListPanel.fxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/view/RecordListCard.fxml b/src/main/resources/view/RecordListCard.fxml new file mode 100644 index 00000000000..8a15aabef9f --- /dev/null +++ b/src/main/resources/view/RecordListCard.fxml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +