Inject promptForYaml instead of having the callers inject it at the call site

main
Mari 3 years ago
parent d3d820e60a
commit a9cbd24e0c
  1. 6
      gulpfile.js
  2. 4121
      package-lock.json
  3. 20
      package.json
  4. 13
      src/commands/AddEntry.ts
  5. 10
      src/commands/UpdateEmpathyGuide.ts
  6. 16
      src/prompts/implementations/EmpathyGroupPrompt.ts
  7. 14
      src/prompts/implementations/EmpathyGuidePrompt.ts
  8. 14
      src/prompts/implementations/EntryMainMenuPrompt.ts
  9. 9
      src/schemata/SchemaData.ts
  10. 33
      src/schemata/YAMLPrompt.ts

@ -0,0 +1,6 @@
const gulp = require("gulp");
const ts = require("gulp-typescript");
const tsProject = ts.createProject("tsconfig.json");
gulp.task("default", function () {
return tsProject.src().pipe(tsProject()).js.pipe(gulp.dest("dist"));
});

4121
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -4,20 +4,16 @@
"description": "The guided journaling software used to track Mari's status.", "description": "The guided journaling software used to track Mari's status.",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"start": "npm run app", "start": "npm run --silent app",
"app": "node dist/index.js", "app": "node dist/index.js",
"update-empathy-guide": "npm run app -- update-empathy-guide", "update-empathy-guide": "npm run --silent app -- update-empathy-guide",
"add-entry": "npm run app -- add-entry", "add-entry": "npm run --silent app -- add-entry",
"build": "npm run build-ts", "build": "gulp",
"prestart": "npm run build", "prestart": "npm run --silent build",
"debug": "npm run start --inspect", "debug": "npm run --silent start --inspect"
"watch-node": "nodemon dist/index.js",
"watch": "tsc && concurrently -k -p \"[{name}]\" -n \"TypeScript,Node\" -c \"yellow.bold,cyan.bold,green.bold\" \"npm run watch-ts\" \"npm run watch-node\"",
"build-ts": "tsc",
"watch-ts": "tsc -w"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "Mari",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"ajv": "^8.6.2", "ajv": "^8.6.2",
@ -46,6 +42,8 @@
"@types/pluralize": "^0.0.29", "@types/pluralize": "^0.0.29",
"@types/yargs": "^17.0.0", "@types/yargs": "^17.0.0",
"concurrently": "^5.0.0", "concurrently": "^5.0.0",
"gulp": "^4.0.2",
"gulp-typescript": "^6.0.0-alpha.1",
"nodemon": "^2.0.4", "nodemon": "^2.0.4",
"typescript": "^4.0.3" "typescript": "^4.0.3"
} }

@ -11,6 +11,7 @@ import {conditionPrompt} from "../prompts/implementations/ConditionPrompt";
import {entryPrompt} from "../prompts/implementations/EntryPrompt"; import {entryPrompt} from "../prompts/implementations/EntryPrompt";
import {guidedEmpathyPrompt} from "../prompts/implementations/GuidedEmpathyPrompt"; import {guidedEmpathyPrompt} from "../prompts/implementations/GuidedEmpathyPrompt";
import {suicidalityPrompt} from "../prompts/implementations/SuicidalityPrompt"; import {suicidalityPrompt} from "../prompts/implementations/SuicidalityPrompt";
import {yamlPrompt} from "../schemata/YAMLPrompt";
export function addEntryCommand(): CommandModule { export function addEntryCommand(): CommandModule {
return { return {
@ -34,6 +35,11 @@ export function addEntryCommand(): CommandModule {
const mainMenuItems = [ const mainMenuItems = [
// TODO: add ability to amend previous entry // TODO: add ability to amend previous entry
// TODO: add To-Do list items, which can be in four states (Pending, Done, Suspended, Canceled)
// A list item with the same ID as a previous one overrides it, allowing items to be edited or
// marked as done - the current state of the list is written to a separate part of the journal
// file (or a separate file) on save, so that we can easily edit it on future visits, but the
// entry also contains a changelog of entries added, changed, and removed
condition.mainMenu, condition.mainMenu,
summary.mainMenu, summary.mainMenu,
new Separator(), new Separator(),
@ -83,10 +89,15 @@ export function addEntryCommand(): CommandModule {
*/ */
] ]
const promptYaml = yamlPrompt({
inquire,
showError: async (text: string) => console.log(text)
})
const mainMenu = entryMainMenuPrompt({ const mainMenu = entryMainMenuPrompt({
inquire, inquire,
choices: mainMenuItems, choices: mainMenuItems,
showError: async (value) => console.log(value), promptYaml
}) })
const entry = entryPrompt({ const entry = entryPrompt({

@ -5,6 +5,7 @@ import {LocalRepository} from "../repository/LocalRepository";
import {empathyGroupPrompt} from "../prompts/implementations/EmpathyGroupPrompt"; import {empathyGroupPrompt} from "../prompts/implementations/EmpathyGroupPrompt";
import {empathyGroupListPrompt} from "../prompts/implementations/EmpathyGroupListPrompt"; import {empathyGroupListPrompt} from "../prompts/implementations/EmpathyGroupListPrompt";
import {empathyGuidePrompt} from "../prompts/implementations/EmpathyGuidePrompt"; import {empathyGuidePrompt} from "../prompts/implementations/EmpathyGuidePrompt";
import {yamlPrompt} from "../schemata/YAMLPrompt";
export function updateEmpathyGuideCommand(): CommandModule { export function updateEmpathyGuideCommand(): CommandModule {
return { return {
@ -15,11 +16,16 @@ export function updateEmpathyGuideCommand(): CommandModule {
const storage = new LocalRepository() const storage = new LocalRepository()
const empathyGuide = await storage.loadEmpathyGuide() const empathyGuide = await storage.loadEmpathyGuide()
const empathyGroup = empathyGroupPrompt({ const promptYaml = yamlPrompt({
inquire, inquire,
showError: async (text) => console.error(text), showError: async (text) => console.error(text),
}) })
const empathyGroup = empathyGroupPrompt({
inquire,
promptYaml,
})
const empathyList = empathyGroupListPrompt({ const empathyList = empathyGroupListPrompt({
inquire, inquire,
promptForEmpathyGroup: empathyGroup promptForEmpathyGroup: empathyGroup
@ -27,7 +33,7 @@ export function updateEmpathyGuideCommand(): CommandModule {
const newGuide = await empathyGuidePrompt({ const newGuide = await empathyGuidePrompt({
inquire, inquire,
showError: async (text) => console.error(text), promptYaml,
promptForEmpathyGroupList: empathyList, promptForEmpathyGroupList: empathyList,
})({default: empathyGuide}) })({default: empathyGuide})
await storage.saveEmpathyGuide(newGuide) await storage.saveEmpathyGuide(newGuide)

@ -1,9 +1,9 @@
import capitalize from "capitalize"; import capitalize from "capitalize";
import {editYaml, EditYamlInquire} from "../../schemata/YAMLPrompt"; import {YamlPromptFunction} from "../../schemata/YAMLPrompt";
import chalk from "chalk"; import chalk from "chalk";
import {InquireFunction, ShowFunction} from "../Inquire"; import {InquireFunction} from "../Inquire";
import {EmpathyGroup, EmpathyGroupJTD} from "../../datatypes/EmpathyGroup"; import {EmpathyGroup, EmpathyGroupJTD} from "../../datatypes/EmpathyGroup";
import {ExpandQuestion, InputQuestion, MultiTextInputQuestion} from "inquirer"; import {EditorQuestion, ExpandQuestion, InputQuestion, MultiTextInputQuestion} from "inquirer";
export interface EmpathyGroupPromptOptions { export interface EmpathyGroupPromptOptions {
default?: EmpathyGroup default?: EmpathyGroup
@ -11,8 +11,8 @@ export interface EmpathyGroupPromptOptions {
} }
export interface EmpathyGroupPromptDependencies { export interface EmpathyGroupPromptDependencies {
inquire: InquireFunction<ExpandQuestion, typeof PROMPT|typeof AUTO|typeof YAML> & EditYamlInquire & InquireFunction<InputQuestion, string> & InquireFunction<MultiTextInputQuestion, string[]> inquire: InquireFunction<ExpandQuestion, typeof PROMPT|typeof AUTO|typeof YAML> & InquireFunction<InputQuestion, string> & InquireFunction<MultiTextInputQuestion, string[]> & InquireFunction<EditorQuestion, string>
showError: ShowFunction promptYaml: YamlPromptFunction
} }
export function empathyGroupPrompt(deps: EmpathyGroupPromptDependencies): (opts: EmpathyGroupPromptOptions) => Promise<EmpathyGroup | null> { export function empathyGroupPrompt(deps: EmpathyGroupPromptDependencies): (opts: EmpathyGroupPromptOptions) => Promise<EmpathyGroup | null> {
@ -25,7 +25,7 @@ const YAML = "YAML"
export async function promptForEmpathyGroup(opts: EmpathyGroupPromptOptions, deps: EmpathyGroupPromptDependencies): Promise<EmpathyGroup | null> { export async function promptForEmpathyGroup(opts: EmpathyGroupPromptOptions, deps: EmpathyGroupPromptDependencies): Promise<EmpathyGroup | null> {
const {listName, default: {header = undefined, items = []} = {}} = opts const {listName, default: {header = undefined, items = []} = {}} = opts
const {inquire, showError} = deps const {inquire, promptYaml} = deps
const mode = await inquire({ const mode = await inquire({
type: "expand", type: "expand",
message: `How would you like to ${header ? "edit" : "create"} this ${listName} group?`, message: `How would you like to ${header ? "edit" : "create"} this ${listName} group?`,
@ -52,12 +52,10 @@ export async function promptForEmpathyGroup(opts: EmpathyGroupPromptOptions, dep
items: parsedItems.map((item) => item.toLocaleLowerCase()) items: parsedItems.map((item) => item.toLocaleLowerCase())
} }
case YAML: case YAML:
return await editYaml({ return await promptYaml({
schema: EmpathyGroupJTD, schema: EmpathyGroupJTD,
currentValue: opts.default, currentValue: opts.default,
name: `this ${listName} group`, name: `this ${listName} group`,
inquire,
showError,
}) || null }) || null
case PROMPT: case PROMPT:
default: default:

@ -3,8 +3,8 @@ import chalk from "chalk";
import pluralize from "pluralize"; import pluralize from "pluralize";
import {totalItems} from "../../datatypes/EmpathyGroupList"; import {totalItems} from "../../datatypes/EmpathyGroupList";
import {ExpandQuestion, Separator} from "inquirer"; import {ExpandQuestion, Separator} from "inquirer";
import {editYaml, EditYamlInquire} from "../../schemata/YAMLPrompt"; import {YamlPromptFunction} from "../../schemata/YAMLPrompt";
import {InquireFunction, ShowFunction} from "../Inquire"; import {InquireFunction} from "../Inquire";
import {EmpathyGroupListPromptOptions} from "./EmpathyGroupListPrompt"; import {EmpathyGroupListPromptOptions} from "./EmpathyGroupListPrompt";
import {EmpathyGroup} from "../../datatypes/EmpathyGroup"; import {EmpathyGroup} from "../../datatypes/EmpathyGroup";
import {EmpathyGuide, EmpathyGuideJTD} from "../../datatypes/EmpathyGuide"; import {EmpathyGuide, EmpathyGuideJTD} from "../../datatypes/EmpathyGuide";
@ -14,9 +14,9 @@ export interface EmpathyGuidePromptOptions {
} }
export interface EmpathyGuidePromptDependencies { export interface EmpathyGuidePromptDependencies {
readonly inquire: InquireFunction<ExpandQuestion, typeof PLEASANT | typeof UNPLEASANT | typeof NEEDS | typeof INSPECT | typeof SAVE> & EditYamlInquire readonly inquire: InquireFunction<ExpandQuestion, typeof PLEASANT | typeof UNPLEASANT | typeof NEEDS | typeof INSPECT | typeof SAVE>
readonly showError: ShowFunction
readonly promptForEmpathyGroupList: (opts: EmpathyGroupListPromptOptions) => Promise<readonly EmpathyGroup[]> readonly promptForEmpathyGroupList: (opts: EmpathyGroupListPromptOptions) => Promise<readonly EmpathyGroup[]>
readonly promptYaml: YamlPromptFunction
} }
export function empathyGuidePrompt(deps: EmpathyGuidePromptDependencies): (opts: EmpathyGuidePromptOptions) => Promise<EmpathyGuide> { export function empathyGuidePrompt(deps: EmpathyGuidePromptDependencies): (opts: EmpathyGuidePromptOptions) => Promise<EmpathyGuide> {
@ -31,7 +31,7 @@ const SAVE = "Save"
export async function promptForEmpathyGuide(opts: EmpathyGuidePromptOptions, deps: EmpathyGuidePromptDependencies): Promise<EmpathyGuide> { export async function promptForEmpathyGuide(opts: EmpathyGuidePromptOptions, deps: EmpathyGuidePromptDependencies): Promise<EmpathyGuide> {
const {default: value} = opts const {default: value} = opts
const {inquire, showError, promptForEmpathyGroupList} = deps const {inquire, promptForEmpathyGroupList, promptYaml} = deps
const result = await inquire({ const result = await inquire({
type: "expand", type: "expand",
message: "What would you like to modify?", message: "What would you like to modify?",
@ -108,12 +108,10 @@ export async function promptForEmpathyGuide(opts: EmpathyGuidePromptOptions, dep
default: default:
case INSPECT: case INSPECT:
return promptForEmpathyGuide({ return promptForEmpathyGuide({
default: await editYaml({ default: await promptYaml({
schema: EmpathyGuideJTD, schema: EmpathyGuideJTD,
currentValue: value, currentValue: value,
name: "the current empathy guide", name: "the current empathy guide",
inquire,
showError
}) })
}, deps) }, deps)
} }

@ -1,8 +1,8 @@
import {InquireFunction, ShowFunction} from "../Inquire"; import {InquireFunction} from "../Inquire";
import {ExpandChoiceOptions, ExpandQuestion, Separator, SeparatorOptions} from "inquirer"; import {ExpandChoiceOptions, ExpandQuestion, Separator, SeparatorOptions} from "inquirer";
import {merge} from "../../utils/Merge"; import {merge} from "../../utils/Merge";
import {Entry, EntryJTD} from "../../datatypes/Entry"; import {Entry, EntryJTD} from "../../datatypes/Entry";
import {editYaml, EditYamlInquire} from "../../schemata/YAMLPrompt"; import {YamlPromptFunction} from "../../schemata/YAMLPrompt";
const VIEW = ".View" const VIEW = ".View"
const DONE = ".Done" const DONE = ".Done"
@ -59,9 +59,9 @@ export function makeEntryMainMenuChoice<PropertyT extends string & keyof EntryMa
export interface EntryMainMenuOptions extends Omit<Entry, "finishedAt"> {} export interface EntryMainMenuOptions extends Omit<Entry, "finishedAt"> {}
export interface EntryMainMenuDependencies { export interface EntryMainMenuDependencies {
readonly inquire: InquireFunction<ExpandQuestion, typeof DONE | typeof VIEW | EntryMainMenuChoiceKey> & EditYamlInquire readonly inquire: InquireFunction<ExpandQuestion, typeof DONE | typeof VIEW | EntryMainMenuChoiceKey>
readonly choices: readonly (EntryMainMenuChoice<EntryMainMenuChoiceKey>|SeparatorOptions)[] readonly choices: readonly (EntryMainMenuChoice<EntryMainMenuChoiceKey>|SeparatorOptions)[]
readonly showError: ShowFunction readonly promptYaml: YamlPromptFunction
} }
export function entryMainMenuPrompt(deps: EntryMainMenuDependencies): (options: EntryMainMenuOptions) => Promise<Entry> { export function entryMainMenuPrompt(deps: EntryMainMenuDependencies): (options: EntryMainMenuOptions) => Promise<Entry> {
@ -72,7 +72,7 @@ export async function promptForEntryMainMenu(entry: EntryMainMenuOptions, deps:
const { const {
inquire, inquire,
choices, choices,
showError, promptYaml,
} = deps } = deps
const result = await inquire({ const result = await inquire({
type: "expand", type: "expand",
@ -98,12 +98,10 @@ export async function promptForEntryMainMenu(entry: EntryMainMenuOptions, deps:
finishedAt: new Date(), finishedAt: new Date(),
} }
} else if (result === VIEW) { } else if (result === VIEW) {
const updated = await editYaml({ const updated = await promptYaml({
schema: EntryJTD, schema: EntryJTD,
currentValue: {...entry, finishedAt: new Date()}, currentValue: {...entry, finishedAt: new Date()},
name: "the current entry", name: "the current entry",
inquire,
showError,
}) })
const withoutFinishedAt = {...updated, finishedAt: undefined} const withoutFinishedAt = {...updated, finishedAt: undefined}
return continueWith(withoutFinishedAt) return continueWith(withoutFinishedAt)

@ -8,7 +8,6 @@ interface BaseSchemaData<RepresentedT, ReferenceT extends string> {
schema: unknown, schema: unknown,
key: ReferenceT, key: ReferenceT,
definition: { [key in ReferenceT]: unknown } definition: { [key in ReferenceT]: unknown }
referenced?: { [key in ReferenceT]: RepresentedT }
reference: { "ref": ReferenceT }, reference: { "ref": ReferenceT },
validate: ValidateFunction<RepresentedT> validate: ValidateFunction<RepresentedT>
requiredReferences: AnyReferenceList requiredReferences: AnyReferenceList
@ -19,14 +18,13 @@ export interface SchemaData<RepresentedT, ReferenceT extends string, ReferencesT
schema: JTDSchemaType<RepresentedT, DefinitionsT>, schema: JTDSchemaType<RepresentedT, DefinitionsT>,
key: ReferenceT, key: ReferenceT,
definition: { [key in ReferenceT]: JTDSchemaType<RepresentedT, DefinitionsT> } definition: { [key in ReferenceT]: JTDSchemaType<RepresentedT, DefinitionsT> }
referenced?: { [key in ReferenceT]: RepresentedT }
reference: { "ref": ReferenceT }, reference: { "ref": ReferenceT },
validate: ValidateFunction<RepresentedT> validate: ValidateFunction<RepresentedT>
requiredReferences: ReferencesT requiredReferences: ReferencesT
} }
export type AnySchemaData = BaseSchemaData<unknown, string>
export type AnySchemaDataFor<RepresentedT> = BaseSchemaData<RepresentedT, string> export type AnySchemaDataFor<RepresentedT> = BaseSchemaData<RepresentedT, string>
export type AnySchemaData = AnySchemaDataFor<unknown>
export type AnyReferenceList = AnySchemaData[] export type AnyReferenceList = AnySchemaData[]
export type AnyDefinitions = Record<string, unknown> export type AnyDefinitions = Record<string, unknown>
@ -35,19 +33,16 @@ export type Value<DataT extends AnySchemaData> = Exclude<DataT["value"], undefin
export type Definition<DataT extends AnySchemaData> = Exclude<DataT["definition"], undefined> export type Definition<DataT extends AnySchemaData> = Exclude<DataT["definition"], undefined>
export type Reference<DataT extends AnySchemaData> = Exclude<DataT["reference"], undefined> export type Reference<DataT extends AnySchemaData> = Exclude<DataT["reference"], undefined>
export type ReferenceKey<DataT extends AnySchemaData> = Exclude<DataT["key"], undefined> export type ReferenceKey<DataT extends AnySchemaData> = Exclude<DataT["key"], undefined>
export type Referenced<DataT extends AnySchemaData> = Exclude<DataT["referenced"], undefined>
export type ReferencedSchemaMap<ReferencesT extends AnyReferenceList> = { export type ReferencedSchemaMap<ReferencesT extends AnyReferenceList> = {
[Property in keyof ReferencesT as ReferencesT[Property] extends AnySchemaData ? ReferenceKey<ReferencesT[Property]> : never]: ReferencesT[Property] extends AnySchemaData ? ReferencesT[Property] : never [Property in keyof ReferencesT as ReferencesT[Property] extends AnySchemaData ? ReferenceKey<ReferencesT[Property]> : never]: ReferencesT[Property] extends AnySchemaData ? ReferencesT[Property] : never
} }
export type ReferencedSchema<ReferencesT extends AnyReferenceList> = ReferencedSchemaMap<ReferencesT>[keyof ReferencedSchemaMap<ReferencesT>] export type ReferencedSchema<ReferencesT extends AnyReferenceList> = ReferencedSchemaMap<ReferencesT>[keyof ReferencedSchemaMap<ReferencesT>]
export type ReferencedDefinitions<ReferencesT extends AnyReferenceList> = {
[Property in keyof ReferencedSchemaMap<ReferencesT>]?: Definition<ReferencedSchemaMap<ReferencesT>[Property]>
}
export type ReferencedTypes<ReferencesT extends AnyReferenceList> = { export type ReferencedTypes<ReferencesT extends AnyReferenceList> = {
[Property in keyof ReferencedSchemaMap<ReferencesT>]?: Value<ReferencedSchemaMap<ReferencesT>[Property]> [Property in keyof ReferencedSchemaMap<ReferencesT>]?: Value<ReferencedSchemaMap<ReferencesT>[Property]>
} }
// TODO: Add a test for a circular definition (i.e., an object that contains a reference to itself, for recursive objects like trees) in both one- and two-object loops
export function schema< export function schema<
RepresentedT, RepresentedT,
KeyT extends string, KeyT extends string,

@ -6,12 +6,35 @@ import {EditorQuestion, ExpandQuestion} from "inquirer";
const RETRY = "Retry" const RETRY = "Retry"
const ABORT = "Abort" const ABORT = "Abort"
export type EditYamlInquire = InquireFunction<EditorQuestion, string> & InquireFunction<ExpandQuestion, typeof RETRY|typeof ABORT> export interface YamlPromptOptions<ObjectT> {
schema: AnySchemaDataFor<ObjectT>
currentValue: ObjectT|undefined
name: string
}
export interface YamlPromptSetOptions<ObjectT> extends YamlPromptOptions<ObjectT> {
currentValue: ObjectT
}
export interface YamlPromptDependencies {
inquire: InquireFunction<EditorQuestion, string> & InquireFunction<ExpandQuestion, typeof RETRY|typeof ABORT>
showError: ShowFunction
}
export type YamlPromptFunction = (<ObjectT>(opts: YamlPromptSetOptions<ObjectT>) => Promise<ObjectT>) & (<ObjectT>(opts: YamlPromptOptions<ObjectT>) => Promise<ObjectT|undefined>)
export function yamlPrompt(deps: YamlPromptDependencies): YamlPromptFunction {
function injectedYamlPrompt<ObjectT>(opts: YamlPromptSetOptions<ObjectT>): Promise<ObjectT>
function injectedYamlPrompt<ObjectT>(opts: YamlPromptOptions<ObjectT>): Promise<ObjectT|undefined>
function injectedYamlPrompt<ObjectT>(opts: YamlPromptOptions<ObjectT>): Promise<ObjectT|undefined> {
return promptForYaml<ObjectT>(opts, deps)
}
return injectedYamlPrompt
}
// TODO: Inject this one's dependencies (inquire, showError) as well, instead of making callers do it, and give it to said callers. export async function promptForYaml<ObjectT>({schema, currentValue, name}: YamlPromptSetOptions<ObjectT>, {inquire, showError}: YamlPromptDependencies): Promise<ObjectT>
export async function editYaml<ObjectT extends object>({schema, currentValue, name, inquire, showError}: {schema: AnySchemaDataFor<ObjectT>, currentValue: ObjectT, name: string, inquire: EditYamlInquire, showError: ShowFunction}): Promise<ObjectT> export async function promptForYaml<ObjectT>({schema, currentValue, name}: YamlPromptOptions<ObjectT>, {inquire, showError}: YamlPromptDependencies): Promise<ObjectT|undefined>
export async function editYaml<ObjectT extends object>({schema, currentValue, name, inquire, showError}: {schema: AnySchemaDataFor<ObjectT>, currentValue: ObjectT|undefined, name: string, inquire: EditYamlInquire, showError: ShowFunction}): Promise<ObjectT|undefined> export async function promptForYaml<ObjectT>({schema, currentValue, name}: YamlPromptOptions<ObjectT>, {inquire, showError}: YamlPromptDependencies): Promise<ObjectT|undefined> {
export async function editYaml<ObjectT extends object>({schema, currentValue, name, inquire, showError}: {schema: AnySchemaDataFor<ObjectT>, currentValue: ObjectT|undefined, name: string, inquire: EditYamlInquire, showError: ShowFunction}): Promise<ObjectT|undefined> {
const original = dump(currentValue) const original = dump(currentValue)
let text = original let text = original
while (true) { while (true) {

Loading…
Cancel
Save