diff --git a/misc_docs/syntax/controlflow_ifelse.mdx b/misc_docs/syntax/controlflow_ifelse.mdx new file mode 100644 index 000000000..4bdbb9ede --- /dev/null +++ b/misc_docs/syntax/controlflow_ifelse.mdx @@ -0,0 +1,20 @@ +--- +id: "if-else" +keywords: ["if", "else"] +name: "if / else" +summary: "This is the `if / else` control flow." +category: "controlflow" +--- + +Use `if / else` expressions to express a value trough a `true` / `false` condition. + +```res +let user = "Anna" + +let ret = if user === "Anna" { + "Hi Anna!" +} +else { + "Hi unknown!" +} +``` diff --git a/misc_docs/syntax/decorator_as.mdx b/misc_docs/syntax/decorator_as.mdx new file mode 100644 index 000000000..85eb5cd29 --- /dev/null +++ b/misc_docs/syntax/decorator_as.mdx @@ -0,0 +1,36 @@ +--- +id: "as-decorator" +keywords: ["as", "decorator"] +name: "@as" +summary: "This is the `@as` decorator." +category: "decorators" +--- + +The `@as` decorator is commonly used on record types to alias record field names to a different JavaScript attribute name. + +This is useful to map to JavaScript attribute names that cannot be expressed in ReScript (such as keywords). + +### Example + + + +```res +type action = { + @as("type") type_: string +} + +let action = {type_: "ADD_USER"} +``` + +```js +var action = { + type: "ADD_USER" +}; +``` + + + +### References + +* [Constrain Arguments Better](/docs/manual/latest/bind-to-js-function#constrain-arguments-better) +* [Fixed Arguments](/docs/manual/latest/bind-to-js-function#fixed-arguments) diff --git a/misc_docs/syntax/decorator_deriving.mdx b/misc_docs/syntax/decorator_deriving.mdx new file mode 100644 index 000000000..a686a3e86 --- /dev/null +++ b/misc_docs/syntax/decorator_deriving.mdx @@ -0,0 +1,52 @@ +--- +id: "deriving-decorator" +keywords: ["deriving", "decorator"] +name: "@deriving" +summary: "This is the `@deriving` decorator." +category: "decorators" +--- + +When the `@deriving` decorator is applied to a **record** type, +it expands the type into a factory function plus a set of +getter/setter functions for its fields. + +> Note that this is an outdated decorator and you may no longer need to use it. +> See [Convert Record Type to Abstract Record](/docs/manual/latest/generate-converters-accessors#convert-record-type-to-abstract-record) for more details. + +### Example + + + +```res +@deriving(abstract) +type person = { + name: string, + age: int, + job: string, +} + +let joe = person(~name="Joe", ~age=20, ~job="teacher") + +let joeName = nameGet(joe) +let joeAge = ageGet(joe) +let joeJob = jobGet(joe) +``` + +```js +var joe = { + name: "Joe", + age: 20, + job: "teacher" +}; + +var joeName = joe.name; +var joeAge = joe.age; +var joeJob = joe.job; +``` + + + +### References + +* [Convert Record Type to Abstract Record](/docs/manual/latest/generate-converters-accessors#convert-record-type-to-abstract-record) + diff --git a/misc_docs/syntax/decorator_get.mdx b/misc_docs/syntax/decorator_get.mdx new file mode 100644 index 000000000..c3d69e230 --- /dev/null +++ b/misc_docs/syntax/decorator_get.mdx @@ -0,0 +1,31 @@ +--- +id: "get-decorator" +keywords: ["get", "decorator"] +name: "@get" +summary: "This is the `@get` decorator." +category: "decorators" +--- + +The `@get` decorator is used to bind to a property of an object. + +### Example + + + +```res +type window +@bs.val external window: window = "window" +@bs.get external getName: window => string = "name" + +let name = getName(window) +``` + +```js +var name = window.name; +``` + + + +### References + +- [Bind using Special `@bs` Getters & Setters](/docs/manual/latest/bind-to-js-object#bind-using-special-bs-getters--setters) diff --git a/misc_docs/syntax/decorator_get_index.mdx b/misc_docs/syntax/decorator_get_index.mdx new file mode 100644 index 000000000..8eec67fed --- /dev/null +++ b/misc_docs/syntax/decorator_get_index.mdx @@ -0,0 +1,45 @@ +--- +id: "get-index-decorator" +keywords: ["get", "index", "decorator"] +name: "@get_index" +summary: "This is the `@get_index` decorator." +category: "decorators" +--- + +The `@get_index` decorator is used to access a dynamic property on an object, +or an index of an array. + +### Example + + + +```res +type t + +@new external create: unit => t = "Object" +@set_index external set: (t, string, int) => unit = "" +@get_index external get: (t, string) => int = "" + +let o = create() +o->set("x", 1) +o->set("y", 3) +o->set("z", 5) + +let value = o->get("y") +``` + +```js +var o = new Object(); + +o["x"] = 1; +o["y"] = 3; +o["z"] = 5; + +var value = o["y"]; +``` + + + +### References + +- [Bind using Special `@bs` Getters & Setters](/docs/manual/latest/bind-to-js-object#bind-using-special-bs-getters--setters) diff --git a/misc_docs/syntax/decorator_inline.mdx b/misc_docs/syntax/decorator_inline.mdx new file mode 100644 index 000000000..5f630ae19 --- /dev/null +++ b/misc_docs/syntax/decorator_inline.mdx @@ -0,0 +1,39 @@ +--- +id: "inline-decorator" +keywords: ["inline", "decorator"] +name: "@inline" +summary: "This is the `@inline` decorator." +category: "decorators" +--- + +The `@inline` decorator tells the compiler to inline its value +in every place the binding is being used, rather than use a variable. + +### Example + + + +```res +module Colors = { + @inline + let green = "green" + + @inline + let red = "red" +} + +let allowedColors = [Colors.green, Colors.red] +``` + +```js +var allowedColors = [ + "green", + "red" +]; +``` + + + +### References + +- [Inlining Constants](/docs/manual/latest/inlining-constants) diff --git a/misc_docs/syntax/decorator_int.mdx b/misc_docs/syntax/decorator_int.mdx new file mode 100644 index 000000000..8cbd6d4ac --- /dev/null +++ b/misc_docs/syntax/decorator_int.mdx @@ -0,0 +1,35 @@ +--- +id: "int-decorator" +keywords: ["int", "decorator"] +name: "@int" +summary: "This is the `@int` decorator." +category: "decorators" +--- + +The `@int` decorator can be used with [polymorphic variants](/docs/manual/latest/polymorphic-variant) and the `@as` decorator on *externals* to modify the compiled JavaScript to use integers for the values instead of strings. + +### Example + + + +```res +@val external setStatus: @int[ + @as(0) #NotStarted | + @as(1) #Started | + @as(2) #Done +] => unit = "setStatus" + +setStatus(#Done) +``` + +```js +setStatus(2); +``` + + + +### References + +* [Constrain Arguments Better](/docs/manual/latest/bind-to-js-function#constrain-arguments-better) + + diff --git a/misc_docs/syntax/decorator_meth.mdx b/misc_docs/syntax/decorator_meth.mdx new file mode 100644 index 000000000..306182d4e --- /dev/null +++ b/misc_docs/syntax/decorator_meth.mdx @@ -0,0 +1,45 @@ +--- +id: "meth-decorator" +keywords: ["meth", "decorator"] +name: "@meth" +summary: "This is the `@meth` decorator." +category: "decorators" +--- + +The `@meth` decorator is used to call a function on a JavaScript object, +and avoid issues with currying. + +### Example + +Suppose we have the following JavaScript: + +```js +function say (a, b) { + console.log(a, b); +}; + +var john = { + say +}; +``` + +We can model and bind to this object as follows. + + + +```res +type person = {@meth "say": (string, string) => unit} + +@val external john: person = "john" + +john["say"]("hey", "jude") +``` + +```js +john.say("hey", "jude"); +``` + + + + + diff --git a/misc_docs/syntax/decorator_module.mdx b/misc_docs/syntax/decorator_module.mdx new file mode 100644 index 000000000..5847a500a --- /dev/null +++ b/misc_docs/syntax/decorator_module.mdx @@ -0,0 +1,32 @@ +--- +id: "module-decorator" +keywords: ["module", "decorator"] +name: "@module" +summary: "This is the `@module` decorator." +category: "decorators" +--- + +The `@module` decorator is used to bind to a JavaScript module. + +### Example + + + +```res +@module("path") +external dirname: string => string = "dirname" + +let root = dirname("/User/github") +``` + +```js +var Path = require("path"); + +var root = Path.dirname("/User/github"); +``` + + + +### References + +* [Import from JavaScript](/docs/manual/latest/import-from-export-to-js#import-from-javascript) diff --git a/misc_docs/syntax/decorator_new.mdx b/misc_docs/syntax/decorator_new.mdx new file mode 100644 index 000000000..056a16fcd --- /dev/null +++ b/misc_docs/syntax/decorator_new.mdx @@ -0,0 +1,32 @@ +--- +id: "new-decorator" +keywords: ["new", "decorator"] +name: "@new" +summary: "This is the `@new` decorator." +category: "decorators" +--- + +The `@new` decorator is used whenever you need to bind to a JavaScript +class constructor that requires the `new` keword for instantiation. + +### Example + + + +```res +type t + +@new external create: unit => t = "Date" + +let now = create() +``` + +```js +var now = new Date(); +``` + + + +### References + +* [Bind to a JS Object That's a Class](/docs/manual/latest/bind-to-js-object#bind-to-a-js-object-thats-a-class) \ No newline at end of file diff --git a/misc_docs/syntax/decorator_obj.mdx b/misc_docs/syntax/decorator_obj.mdx new file mode 100644 index 000000000..ae6e14d39 --- /dev/null +++ b/misc_docs/syntax/decorator_obj.mdx @@ -0,0 +1,35 @@ +--- +id: "obj-decorator" +keywords: ["obj", "decorator"] +name: "@obj" +summary: "This is the `@obj` decorator." +category: "decorators" +--- + +The `@obj` decorator is used to create functions that return JavaScript objects +with properties that match the function's parameter labels. + +### Example + + + +```res +@bs.obj +external action: (~name: string, unit) => _ = "" + +let helloAction = action(~name="Hello") +``` + +```js +function helloAction(param) { + return { + name: "Hello", + } +} +``` + + + +### References + +* [Convert External into JS Object Creation Function](/docs/manual/latest/generate-converters-accessors#convert-external-into-js-object-creation-function) \ No newline at end of file diff --git a/misc_docs/syntax/decorator_return.mdx b/misc_docs/syntax/decorator_return.mdx new file mode 100644 index 000000000..3c5da1e31 --- /dev/null +++ b/misc_docs/syntax/decorator_return.mdx @@ -0,0 +1,46 @@ +--- +id: "return-decorator" +keywords: ["return", "decorator"] +name: "@return" +summary: "This is the `@return` decorator." +category: "decorators" +--- + +The `@return` decorator is used to control how `null` and `undefined` values are converted to `option` types in ReScript. + +### Example + + + +```res +type element +type dom + +@send @return(nullable) +external getElementById: (dom, string) => option = "getElementById" + +let test = dom => { + let elem = dom->getElementById("haha") + switch elem { + | None => 1 + | Some(_) => 2 + } +} +``` + +```js +function test(dom) { + var elem = dom.getElementById("haha"); + if (elem == null) { + return 1; + } else { + return 2; + } +} +``` + + + +### References + +* [Function Nullable Return Value Wrapping](/docs/manual/latest/bind-to-js-function#function-nullable-return-value-wrapping) \ No newline at end of file diff --git a/misc_docs/syntax/decorator_scope.mdx b/misc_docs/syntax/decorator_scope.mdx new file mode 100644 index 000000000..548bd8cee --- /dev/null +++ b/misc_docs/syntax/decorator_scope.mdx @@ -0,0 +1,30 @@ +--- +id: "scope-decorator" +keywords: ["scope", "decorator"] +name: "@scope" +summary: "This is the `@scope` decorator." +category: "decorators" +--- + +The `@scope` decorator is used with other decorators such as `@val` and `@module` to declare a parent scope for the binding. + +### Example + + + +```res +@scope("Math") @val +external floor: float => int = "floor" + +let result = floor(3.4) +``` + +```js +var result = Math.floor(3.4); +``` + + + +### References + +* [Global Modules](/docs/manual/latest/bind-to-global-js-values#global-modules) \ No newline at end of file diff --git a/misc_docs/syntax/decorator_send.mdx b/misc_docs/syntax/decorator_send.mdx new file mode 100644 index 000000000..74ac8b1b3 --- /dev/null +++ b/misc_docs/syntax/decorator_send.mdx @@ -0,0 +1,31 @@ +--- +id: "send-decorator" +keywords: ["send", "decorator"] +name: "@send" +summary: "This is the `@send` decorator." +category: "decorators" +--- + +The `@send` decorator is used to bind to a method on an object. + +### Example + + + +```res +type document +@bs.send external getElementById: (document, string) => Dom.element = "getElementById" +@bs.val external doc: document = "document" + +let el = getElementById(doc, "myId") +``` + +```js +var el = document.getElementById("myId"); +``` + + + +### References + +* [Bind to JS Function](/docs/manual/latest/bind-to-js-function) \ No newline at end of file diff --git a/misc_docs/syntax/decorator_set.mdx b/misc_docs/syntax/decorator_set.mdx new file mode 100644 index 000000000..ce2ff896b --- /dev/null +++ b/misc_docs/syntax/decorator_set.mdx @@ -0,0 +1,31 @@ +--- +id: "set-decorator" +keywords: ["set", "decorator"] +name: "@set" +summary: "This is the `@set` decorator." +category: "decorators" +--- + +The `@set` decorator is used to set a property of an object. + +### Example + + + +```res +type window +@bs.val external window: window = "window" +@bs.set external setName: (window, string) => unit = "name" + +setName(window, "MyWindow") +``` + +```js +window.name = "MyWindow"; +``` + + + +### References + +- [Bind using Special `@bs` Getters & Setters](/docs/manual/latest/bind-to-js-object#bind-using-special-bs-getters--setters) diff --git a/misc_docs/syntax/decorator_set_index.mdx b/misc_docs/syntax/decorator_set_index.mdx new file mode 100644 index 000000000..47d27991a --- /dev/null +++ b/misc_docs/syntax/decorator_set_index.mdx @@ -0,0 +1,45 @@ +--- +id: "set-index-decorator" +keywords: ["set", "index", decorator"] +name: "@set_index" +summary: "This is the `@set_index` decorator." +category: "decorators" +--- + +The `@set_index` decorator is used to set a dynamic property on an object, +or an index of an array. + +### Example + + + +```res +type t + +@new external create: unit => t = "Object" +@set_index external set: (t, string, int) => unit = "" +@get_index external get: (t, string) => int = "" + +let o = create() +o->set("x", 1) +o->set("y", 3) +o->set("z", 5) + +let value = o->get("y") +``` + +```js +var o = new Object(); + +o["x"] = 1; +o["y"] = 3; +o["z"] = 5; + +var value = o["y"]; +``` + + + +### References + +- [Bind using Special `@bs` Getters & Setters](/docs/manual/latest/bind-to-js-object#bind-using-special-bs-getters--setters) diff --git a/misc_docs/syntax/decorator_string.mdx b/misc_docs/syntax/decorator_string.mdx new file mode 100644 index 000000000..4b24e7b76 --- /dev/null +++ b/misc_docs/syntax/decorator_string.mdx @@ -0,0 +1,33 @@ +--- +id: "string-decorator" +keywords: ["string", "decorator"] +name: "@string" +summary: "This is the `@string` decorator." +category: "decorators" +--- + +The `@string` decorator can be used with [polymorphic variants](/docs/manual/latest/polymorphic-variant) and the `@as` decorator on *externals* to modify the string values used for the variants in the compiled JavaScript. + +### Example + + + +```res +@val external setStatus: @string[ + @as("NOT_STARTED") #NotStarted | + @as("STARTED") #Started | + @as("DONE") #Done +] => unit = "setStatus" + +setStatus(#NotStarted) +``` + +```js +setStatus("NOT_STARTED"); +``` + + + +### References + +* [Constrain Arguments Better](/docs/manual/latest/bind-to-js-function#constrain-arguments-better) \ No newline at end of file diff --git a/misc_docs/syntax/decorator_unboxed.mdx b/misc_docs/syntax/decorator_unboxed.mdx new file mode 100644 index 000000000..4eb9c7c89 --- /dev/null +++ b/misc_docs/syntax/decorator_unboxed.mdx @@ -0,0 +1,35 @@ +--- +id: "unboxed-decorator" +keywords: ["unboxed", "decorator"] +name: "@unboxed" +summary: "This is the `@unboxed` decorator." +category: "decorators" +--- + +The `@unboxed` decorator provides a way to unwrap **variant** constructors +that have a *single* argument, or **record** objects that have a *single* field. + +### Example + + + +```res +@unboxed +type name = Name(string) +let studentName = Name("Joe") + +@unboxed +type greeting = {message: string} +let hi = {message: "hello!"} +``` + +```js +var studentName = "Joe"; +var hi = "hello!"; +``` + + + +### References + +* [Unboxed](/docs/manual/latest/unboxed) \ No newline at end of file diff --git a/misc_docs/syntax/decorator_unwrap.mdx b/misc_docs/syntax/decorator_unwrap.mdx new file mode 100644 index 000000000..89337556f --- /dev/null +++ b/misc_docs/syntax/decorator_unwrap.mdx @@ -0,0 +1,35 @@ +--- +id: "unwrap-decorator" +keywords: ["unwrap", "decorator"] +name: "@unwrap" +summary: "This is the `@unwrap` decorator." +category: "decorators" +--- + +The `@unwrap` decorator may be used when binding to external functions +that accept multiple types for an argument. + +### Example + + + +```res +@val external padLeft: ( + @unwrap [#Int(int) | #Str(string)], + string +) => string = "padLeft"; + +let result1 = padLeft(#Int(7), "eleven"); +let result2 = padLeft(#Str("7"), "eleven"); +``` + +```js +var result1 = padLeft(7, "eleven"); +var result2 = padLeft("7", "eleven"); +``` + + + +### References + +* [Modeling Polymorphic Function](/docs/manual/latest/bind-to-js-function#modeling-polymorphic-function) \ No newline at end of file diff --git a/misc_docs/syntax/decorator_val.mdx b/misc_docs/syntax/decorator_val.mdx new file mode 100644 index 000000000..396621b71 --- /dev/null +++ b/misc_docs/syntax/decorator_val.mdx @@ -0,0 +1,34 @@ +--- +id: "val-decorator" +keywords: ["val", "decorator"] +name: "@val" +summary: "This is the `@val` decorator." +category: "decorators" +--- + +The `@val` decorator allows you to bind to JavaScript values that are on the global scope. + +### Example + + + +```res +type timeoutID + +@val +external setTimeout: (unit => unit, int) => timeoutID = "setTimeout" + +let timeoutID = setTimeout(() => Js.log("Hello"), 1000) +``` + +```js +var timeoutID = setTimeout(function (param) { + console.log("Hello") +}, 1000) +``` + + + +### References + +* [Bind to Global JS Values](/docs/manual/latest/bind-to-global-js-values) diff --git a/misc_docs/syntax/decorator_variadic.mdx b/misc_docs/syntax/decorator_variadic.mdx new file mode 100644 index 000000000..78bc14c2d --- /dev/null +++ b/misc_docs/syntax/decorator_variadic.mdx @@ -0,0 +1,30 @@ +--- +id: "variadic-decorator" +keywords: ["variadic", "decorator"] +name: "@variadic" +summary: "This is the `@variadic` decorator." +category: "decorators" +--- + +The `@variadic` decorator is used to model JavaScript functions that take a variable number of arguments, where all arguments are of the same type. + +### Example + + + +```res +@val @variadic @scope("Math") +external max: array => int = "max" + +let result = max([5, -2, 6, 1]) +``` + +```js +var result = Math.max(5, -2, 6, 1); +``` + + + +### References + +* [Variadic Function Arguments](/docs/manual/latest/bind-to-js-function#variadic-function-arguments) \ No newline at end of file diff --git a/misc_docs/syntax/function_uncurried.mdx b/misc_docs/syntax/function_uncurried.mdx new file mode 100644 index 000000000..d6d0e1503 --- /dev/null +++ b/misc_docs/syntax/function_uncurried.mdx @@ -0,0 +1,11 @@ +--- +test: "foo" +--- + +The `(.) => { ... }` (uncurried) syntax defines a function type that is not curried by default. This is useful whenever you want to make sure that your function is not being curried unintentionally, or if you want to force the compiler to emit cleaner JS source code (JS doesn't have curried functions by default). + +```res +let myFn = (cb: (. int) => unit) => { + cb(. 1) +} +``` diff --git a/misc_docs/syntax/operators_float_addition.mdx b/misc_docs/syntax/operators_float_addition.mdx new file mode 100644 index 000000000..27072c4d5 --- /dev/null +++ b/misc_docs/syntax/operators_float_addition.mdx @@ -0,0 +1,23 @@ +--- +id: "float-addition" +keywords: ["plus", "add", "addition", "sum", "float"] +name: "+." +summary: "This is the `floating point addition` operator." +category: "operators" +--- + +This operator performs *floating point* addition. + + + +```res +let result = 1.3 +. 0.5 +``` + +```js +var result = 1.3 + 0.5; +``` + + + +For adding *integers* see the `+` operator. diff --git a/misc_docs/syntax/operators_float_division.mdx b/misc_docs/syntax/operators_float_division.mdx new file mode 100644 index 000000000..899e0eac0 --- /dev/null +++ b/misc_docs/syntax/operators_float_division.mdx @@ -0,0 +1,23 @@ +--- +id: "float-division" +keywords: ["divide", "division", "float"] +name: "/." +summary: "This is the `floating point division` operator." +category: "operators" +--- + +This operator performs *floating point* division. + + + +```res +let result = 3.0 /. 2.5 +``` + +```js +var result = 3.0 / 2.5; +``` + + + +For dividing *integers* see the `/` operator. diff --git a/misc_docs/syntax/operators_float_multiplication.mdx b/misc_docs/syntax/operators_float_multiplication.mdx new file mode 100644 index 000000000..391ad78da --- /dev/null +++ b/misc_docs/syntax/operators_float_multiplication.mdx @@ -0,0 +1,23 @@ +--- +id: "float-multiplication" +keywords: ["multiply", "multiplication", "float"] +name: "*." +summary: "This is the `floating point multiplication` operator." +category: "operators" +--- + +This operator performs *floating point* multiplication. + + + +```res +let result = 1.5 *. 2.3 +``` + +```js +var result = 1.5 * 2.3; +``` + + + +For multiplying *integers* see the `*` operator. diff --git a/misc_docs/syntax/operators_float_subtraction.mdx b/misc_docs/syntax/operators_float_subtraction.mdx new file mode 100644 index 000000000..256754e2e --- /dev/null +++ b/misc_docs/syntax/operators_float_subtraction.mdx @@ -0,0 +1,23 @@ +--- +id: "float-subtraction" +keywords: ["subtract", "minus", "subtraction", "float"] +name: "-." +summary: "This is the `floating point subtraction` operator." +category: "operators" +--- + +This operator performs *floating point* subtraction. + + + +```res +let result = 3.0 -. 2.5 +``` + +```js +var result = 3.0 - 2.5; +``` + + + +For subtracting *integers* see the `-` operator. diff --git a/misc_docs/syntax/operators_integer_addition.mdx b/misc_docs/syntax/operators_integer_addition.mdx new file mode 100644 index 000000000..3d89ab707 --- /dev/null +++ b/misc_docs/syntax/operators_integer_addition.mdx @@ -0,0 +1,25 @@ +--- +id: "integer-addition" +keywords: ["plus", "add", "addition", "sum", "int", "integer"] +name: "+" +summary: "This is the `integer addition` operator." +category: "operators" +--- + +This operator performs *integers* addition. + + + +```res +let result = 1 + 2 +``` + +```js +val result = 3; +``` + + + +For adding *floats* see the `+.` operator. + +For contatenating *strings* see the `++` operator. diff --git a/misc_docs/syntax/operators_integer_division.mdx b/misc_docs/syntax/operators_integer_division.mdx new file mode 100644 index 000000000..7e2670ed5 --- /dev/null +++ b/misc_docs/syntax/operators_integer_division.mdx @@ -0,0 +1,25 @@ +--- +id: "integer-division" +keywords: ["divide", "division", "int", "integer"] +name: "/" +summary: "This is the `integer division` operator." +category: "operators" +--- + +This operator performs *integer* division, with the result truncated to an integer value. + +If the second argument is *zero* then a `Division_by_zero` exception is thrown. Refer to the [Exception](/docs/manual/latest/exception) section for handling exceptions. + + + +```res +let result = 3 / 2 +``` + +```js +var result = 1; +``` + + + +For dividing *floats* see the `/.` operator. diff --git a/misc_docs/syntax/operators_integer_multiplication.mdx b/misc_docs/syntax/operators_integer_multiplication.mdx new file mode 100644 index 000000000..4ea81b129 --- /dev/null +++ b/misc_docs/syntax/operators_integer_multiplication.mdx @@ -0,0 +1,23 @@ +--- +id: "integer-multiplication" +keywords: ["multiply", "multiplication", "int", "integer"] +name: "*" +summary: "This is the `integer multiplication` operator." +category: "operators" +--- + +This operator performs *integer* multiplication. + + + +```res +let result = 2 * 3 +``` + +```js +var result = 6; +``` + + + +For multiplying *floats* see the `*.` operator. diff --git a/misc_docs/syntax/operators_integer_subtraction.mdx b/misc_docs/syntax/operators_integer_subtraction.mdx new file mode 100644 index 000000000..1a090e6dc --- /dev/null +++ b/misc_docs/syntax/operators_integer_subtraction.mdx @@ -0,0 +1,23 @@ +--- +id: "integer-subtraction" +keywords: ["subtract", "minus", "subtraction", "int", "integer"] +name: "-" +summary: "This is the `integer subtraction` operator." +category: "operators" +--- + +This operator performs *integer* subtraction. + + + +```res +let result = 3 - 2 +``` + +```js +var result = 1; +``` + + + +For subtracting *floats* see the `-.` operator. diff --git a/misc_docs/syntax/operators_string_concatenation.mdx b/misc_docs/syntax/operators_string_concatenation.mdx new file mode 100644 index 000000000..0d6b49ac8 --- /dev/null +++ b/misc_docs/syntax/operators_string_concatenation.mdx @@ -0,0 +1,23 @@ +--- +id: "string-concatenation" +keywords: ["concat", "concatenation", "add", "string"] +name: "++" +summary: "This is the `string concatenation` operator." +category: "operators" +--- + +This operator concatenates two *strings* together. + + + +```res +let greetings = "Hello " ++ "world!" +``` + +```js +var greetings = "Hello world!"; +``` + + + + diff --git a/package.json b/package.json index e210e7714..e6774bd40 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "test": "node scripts/test-examples.js && node scripts/test-hrefs.js", "check-dead-code": "reanalyze -dce", "check-for-exceptions": "reanalyze -exception", - "update-index": "node scripts/extract-indices.js && node scripts/extract-tocs.js && node -r esm scripts/generate_feed.js > public/blog/feed.xml", + "update-index": "node scripts/extract-indices.js && node scripts/extract-tocs.js && node scripts/extract-syntax.js && node -r esm scripts/generate_feed.js > public/blog/feed.xml", "now-build": "yarn run build", "export": "next export", "start": "next start -p $PORT", diff --git a/pages/syntax-lookup.mdx b/pages/syntax-lookup.mdx new file mode 100644 index 000000000..8cc91d9d1 --- /dev/null +++ b/pages/syntax-lookup.mdx @@ -0,0 +1,9 @@ +--- +title: "Syntax Lookup" +description: "Discover ReScript syntax constructs with our lookup tool" +canonical: "/docs/manual/latest/syntax-lookup" +--- + +import { make as SyntaxLookup } from "src/SyntaxLookup" + + diff --git a/scripts/extract-syntax.js b/scripts/extract-syntax.js new file mode 100644 index 000000000..22cf0fd7f --- /dev/null +++ b/scripts/extract-syntax.js @@ -0,0 +1,37 @@ +const matter = require("gray-matter"); +const glob = require("glob"); +const path = require("path"); +const fs = require("fs"); + +const processFile = filepath => { + const raw = fs.readFileSync(filepath, "utf8"); + const { data } = matter(raw); + + const syntaxPath = path.resolve("./misc_docs/syntax"); + const relFilePath = path.relative(syntaxPath, filepath); + const parsedPath = path.parse(relFilePath); + + if (data.id && data.keywords && data.name && data.summary && data.category) { + return { + file: parsedPath.name, + id: data.id, + keywords: data.keywords, + name: data.name, + summary: data.summary, + category: data.category + } + } + + console.error("Metadata missing in " + parsedPath.name + ".mdx") + return null; +}; + +const extractSyntax = async version => { + const SYNTAX_MD_DIR = path.join(__dirname, "../misc_docs/syntax"); + const SYNTAX_INDEX_FILE = path.join(__dirname, "../index_data/syntax_index.json"); + const syntaxFiles = glob.sync(`${SYNTAX_MD_DIR}/*.md?(x)`); + const syntaxIndex = syntaxFiles.map(processFile).filter(Boolean).sort((a, b) => a.name.localeCompare(b.name)) + fs.writeFileSync(SYNTAX_INDEX_FILE, JSON.stringify(syntaxIndex), "utf8"); +}; + +extractSyntax() diff --git a/scripts/test-hrefs.js b/scripts/test-hrefs.js index 61c50c0bd..fa1cab264 100644 --- a/scripts/test-hrefs.js +++ b/scripts/test-hrefs.js @@ -206,8 +206,10 @@ const testFile = (pageMap, test) => { const main = () => { const [, , pattern] = process.argv; const cwd = path.join(__dirname, ".."); + + // All files that are going to be tested for broken links const files = glob.sync( - pattern ? pattern : `./{pages,_blogposts}/**/*.md?(x)`, + pattern ? pattern : `./{pages,_blogposts,misc_docs}/**/*.md?(x)`, { cwd } ); diff --git a/src/DocsOverview.js b/src/DocsOverview.js index 7ec9a147c..c4d10ebf7 100644 --- a/src/DocsOverview.js +++ b/src/DocsOverview.js @@ -74,6 +74,10 @@ function DocsOverview$default(Props) { "https://github.com/reason-association/reanalyze" ] ]; + var tools = [[ + "Syntax Lookup", + "/syntax-lookup" + ]]; var versionSelect; if (showVersionSelect) { var onChange = function (evt) { @@ -105,6 +109,9 @@ function DocsOverview$default(Props) { }), React.createElement(DocsOverview$Card, { title: "Ecosystem", hrefs: ecosystem + }), React.createElement(DocsOverview$Card, { + title: "Tools", + hrefs: tools }))); } diff --git a/src/DocsOverview.res b/src/DocsOverview.res index 4b739b8d7..f62665766 100644 --- a/src/DocsOverview.res +++ b/src/DocsOverview.res @@ -39,6 +39,7 @@ let default = (~showVersionSelect=true) => { ("ReasonReact", "https://reasonml.github.io/reason-react"), ("Reanalyze", "https://github.com/reason-association/reanalyze"), ] + let tools = [("Syntax Lookup", "/syntax-lookup")] let versionSelect = if showVersionSelect { let onChange = evt => { @@ -65,6 +66,7 @@ let default = (~showVersionSelect=true) => {
+
} diff --git a/src/Packages.js b/src/Packages.js index 38709ae7d..736c3cc99 100644 --- a/src/Packages.js +++ b/src/Packages.js @@ -14,6 +14,7 @@ import * as Js_null from "bs-platform/lib/es6/js_null.js"; import FuseJs from "fuse.js"; import * as Process from "process"; import * as Markdown from "./components/Markdown.js"; +import * as SearchBox from "./components/SearchBox.js"; import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js"; import * as Navigation from "./components/Navigation.js"; import * as Belt_Option from "bs-platform/lib/es6/belt_Option.js"; @@ -124,102 +125,6 @@ function applySearch(resources, pattern) { return Belt_Array.concat(filteredNpm, filteredUrls); } -function Packages$SearchBox(Props) { - var completionValuesOpt = Props.completionValues; - var value = Props.value; - var onClear = Props.onClear; - var placeholderOpt = Props.placeholder; - var onValueChange = Props.onValueChange; - var completionValues = completionValuesOpt !== undefined ? completionValuesOpt : []; - var placeholder = placeholderOpt !== undefined ? placeholderOpt : ""; - var match = React.useState(function () { - return /* Inactive */1; - }); - var setState = match[1]; - var state = match[0]; - var textInput = React.useRef(null); - var onMouseDownClear = function (evt) { - evt.preventDefault(); - return Curry._1(onClear, undefined); - }; - var onAreaFocus = function (evt) { - var el = evt.target; - var isDiv = (el.type == null); - if (isDiv && state === /* Inactive */1) { - return Belt_Option.forEach(Caml_option.nullable_to_opt(textInput.current), (function (el) { - el.focus(); - - })); - } - - }; - var onFocus = function (param) { - return Curry._1(setState, (function (param) { - return /* Active */0; - })); - }; - var onBlur = function (param) { - return Curry._1(setState, (function (param) { - return /* Inactive */1; - })); - }; - var onKeyDown = function (evt) { - var key = evt.key; - var ctrlKey = evt.ctrlKey; - var full = ( - ctrlKey ? "CTRL+" : "" - ) + key; - switch (full) { - case "Escape" : - return Curry._1(onClear, undefined); - case "Tab" : - if (completionValues.length !== 1) { - return ; - } - var targetValue = Belt_Array.getExn(completionValues, 0); - if (targetValue !== value) { - evt.preventDefault(); - return Curry._1(onValueChange, targetValue); - } else { - return ; - } - default: - return ; - } - }; - var onChange = function (evt) { - evt.preventDefault(); - return Curry._1(onValueChange, evt.target.value); - }; - return React.createElement("div", { - className: ( - state === /* Active */0 ? "border-fire" : "border-fire-40" - ) + " flex items-center border rounded-lg py-4 px-5", - tabIndex: -1, - onFocus: onAreaFocus, - onBlur: onBlur - }, React.createElement(Icon.MagnifierGlass.make, { - className: ( - state === /* Active */0 ? "text-fire" : "text-fire-80" - ) + " w-4 h-4" - }), React.createElement("input", { - ref: textInput, - className: "text-16 outline-none ml-4 w-full", - placeholder: placeholder, - type: "text", - value: value, - onKeyDown: onKeyDown, - onFocus: onFocus, - onChange: onChange - }), React.createElement("button", { - className: value === "" ? "hidden" : "block", - onFocus: onFocus, - onMouseDown: onMouseDownClear - }, React.createElement(Icon.Close.make, { - className: "w-4 h-4 text-fire" - }))); -} - function Packages$Card(Props) { var value = Props.value; var onKeywordSelect = Props.onKeywordSelect; @@ -580,7 +485,7 @@ function $$default(props) { } }, React.createElement(Markdown.H1.make, { children: "Libraries & Bindings" - }), React.createElement(Packages$SearchBox, { + }), React.createElement(SearchBox.make, { value: searchValue, onClear: onClear, placeholder: "Enter a search term, name, keyword, etc", diff --git a/src/Packages.res b/src/Packages.res index 84a1288df..22102751f 100644 --- a/src/Packages.res +++ b/src/Packages.res @@ -123,105 +123,6 @@ module Resource = { } } -module SearchBox = { - @bs.send external focus: Dom.element => unit = "focus" - - type state = - | Active - | Inactive - - @react.component - let make = ( - ~completionValues: array=[], // set of possible values - ~value: string, - ~onClear: unit => unit, - ~placeholder: string="", - ~onValueChange: string => unit, - ) => { - let (state, setState) = React.useState(_ => Inactive) - let textInput = React.useRef(Js.Nullable.null) - - let onMouseDownClear = evt => { - ReactEvent.Mouse.preventDefault(evt) - onClear() - } - - let focusInput = () => - textInput.current->Js.Nullable.toOption->Belt.Option.forEach(el => el->focus) - - let onAreaFocus = evt => { - let el = ReactEvent.Focus.target(evt) - let isDiv = Js.Null_undefined.isNullable(el["type"]) - - if isDiv && state === Inactive { - focusInput() - } - } - - let onFocus = _ => { - setState(_ => Active) - } - - let onBlur = _ => { - setState(_ => Inactive) - } - - let onKeyDown = evt => { - let key = ReactEvent.Keyboard.key(evt) - let ctrlKey = ReactEvent.Keyboard.ctrlKey(evt) - - let full = (ctrlKey ? "CTRL+" : "") ++ key - - switch full { - | "Escape" => onClear() - | "Tab" => - if Js.Array.length(completionValues) === 1 { - let targetValue = Belt.Array.getExn(completionValues, 0) - - if targetValue !== value { - ReactEvent.Keyboard.preventDefault(evt) - onValueChange(targetValue) - } else { - () - } - } - | _ => () - } - } - - let onChange = evt => { - ReactEvent.Form.preventDefault(evt) - let value = ReactEvent.Form.target(evt)["value"] - onValueChange(value) - } - -
- - - -
- } -} - module Card = { @react.component let make = (~value: Resource.t, ~onKeywordSelect: option unit>=?) => { diff --git a/src/SyntaxLookup.js b/src/SyntaxLookup.js new file mode 100644 index 000000000..015e83e4e --- /dev/null +++ b/src/SyntaxLookup.js @@ -0,0 +1,354 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Next from "./bindings/Next.js"; +import * as Curry from "bs-platform/lib/es6/curry.js"; +import * as React from "react"; +import * as Js_dict from "bs-platform/lib/es6/js_dict.js"; +import FuseJs from "fuse.js"; +import * as Markdown from "./components/Markdown.js"; +import * as SearchBox from "./components/SearchBox.js"; +import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js"; +import * as Belt_Option from "bs-platform/lib/es6/belt_Option.js"; +import * as Caml_option from "bs-platform/lib/es6/caml_option.js"; +import * as GithubSlugger from "github-slugger"; + +var indexData = (require('index_data/syntax_index.json')); + +var render = (function(c) { + return React.createElement(c, {}); + }); + +var requireSyntaxFile = (function(name) { + return require("misc_docs/syntax/" + name + ".mdx").default + }); + +function toString(t) { + switch (t) { + case /* Decorators */0 : + return "Decorators"; + case /* ControlFlow */1 : + return "Control Flow"; + case /* Operators */2 : + return "Operators"; + case /* Other */3 : + return "Other"; + + } +} + +function fromString(s) { + switch (s) { + case "controlflow" : + return /* ControlFlow */1; + case "decorators" : + return /* Decorators */0; + case "operators" : + return /* Operators */2; + default: + return /* Other */3; + } +} + +function SyntaxLookup$Category(Props) { + var title = Props.title; + var children = Props.children; + return React.createElement("div", undefined, React.createElement("h3", { + className: "font-sans font-medium text-gray-100 tracking-wide text-14 uppercase mb-2" + }, title), React.createElement("div", { + className: "flex flex-wrap" + }, children)); +} + +function toItem(syntaxData) { + var file = syntaxData.file; + var id = syntaxData.id; + var keywords = syntaxData.keywords; + var name = syntaxData.name; + var summary = syntaxData.summary; + var category = syntaxData.category; + return { + id: id, + keywords: keywords, + name: name, + summary: summary, + category: fromString(category), + component: requireSyntaxFile(file) + }; +} + +var allItems = Belt_Array.map(indexData, toItem); + +var fuseOpts = { + shouldSort: false, + includeScore: true, + threshold: 0.2, + location: 0, + distance: 30, + minMatchCharLength: 1, + keys: [ + "keywords", + "name" + ] +}; + +var fuse = new FuseJs(allItems, fuseOpts); + +function getAnchor(path) { + var match = path.split("#"); + if (match.length !== 2) { + return ; + } else { + return match[1]; + } +} + +function SyntaxLookup$Tag(Props) { + var text = Props.text; + return React.createElement("span", { + className: "hover:bg-fire hover:text-white bg-fire-10-tr py-1 px-3 rounded text-fire text-16" + }, text); +} + +function SyntaxLookup$DetailBox(Props) { + var summary = Props.summary; + var children = Props.children; + var more = summary.split("`"); + var len = more.length; + var summaryEl; + if (len !== 3) { + summaryEl = len !== 0 ? Belt_Array.map(more, (function (s) { + return s; + })) : null; + } else { + var first = more[0]; + var second = more[1]; + var third = more[2]; + summaryEl = Belt_Array.mapWithIndex([ + first, + React.createElement("span", { + className: "text-fire" + }, second), + third + ], (function (i, el) { + return React.createElement("span", { + key: String(i) + }, el); + })); + } + return React.createElement("div", undefined, React.createElement("div", { + className: "text-21 border-b border-fire-40 pb-4 mb-4 font-semibold" + }, summaryEl), React.createElement("div", { + className: "mt-16" + }, children)); +} + +function SyntaxLookup(Props) { + var router = Next.Router.useRouter(undefined); + var match = React.useState(function () { + return /* ShowAll */0; + }); + var setState = match[1]; + var state = match[0]; + React.useEffect((function () { + var anchor = getAnchor(router.asPath); + if (anchor !== undefined) { + Belt_Option.forEach(Caml_option.undefined_to_opt(allItems.find(function (item) { + return GithubSlugger.slug(item.id) === anchor; + })), (function (item) { + return Curry._1(setState, (function (param) { + return { + TAG: 1, + _0: item, + [Symbol.for("name")]: "ShowDetails" + }; + })); + })); + } + + }), []); + React.useEffect((function () { + var match = getAnchor(router.asPath); + var exit = 0; + if (typeof state === "number" || state.TAG === /* ShowFiltered */0) { + exit = 1; + } else { + var item = state._0; + if (match !== undefined) { + var slug = GithubSlugger.slug(item.id); + if (slug !== match) { + Next.Router.replace(router, "syntax-lookup#" + match); + } + + } else { + Next.Router.replace(router, "syntax-lookup#" + GithubSlugger.slug(item.id)); + } + } + if (exit === 1) { + if (match !== undefined) { + Next.Router.replace(router, "syntax-lookup"); + } + + } + + }), [state]); + var onSearchValueChange = function (value) { + return Curry._1(setState, (function (param) { + if (value === "") { + return /* ShowAll */0; + } + var filtered = Belt_Array.map(fuse.search(value), (function (m) { + return m.item; + })); + if (filtered.length !== 1) { + return { + TAG: 0, + _0: value, + _1: filtered, + [Symbol.for("name")]: "ShowFiltered" + }; + } + var item = Belt_Array.getExn(filtered, 0); + if (item.name === value) { + return { + TAG: 1, + _0: item, + [Symbol.for("name")]: "ShowDetails" + }; + } else { + return { + TAG: 0, + _0: value, + _1: filtered, + [Symbol.for("name")]: "ShowFiltered" + }; + } + })); + }; + var details; + if (typeof state === "number" || state.TAG === /* ShowFiltered */0) { + details = null; + } else { + var item = state._0; + details = React.createElement("div", { + className: "mb-16" + }, React.createElement(SyntaxLookup$DetailBox, { + summary: item.summary, + children: render(item.component) + })); + } + var initial = Belt_Array.map([ + /* Decorators */0, + /* Operators */2, + /* ControlFlow */1, + /* Other */3 + ], (function (cat) { + return [ + toString(cat), + [] + ]; + })); + var items; + items = typeof state === "number" ? allItems : ( + state.TAG === /* ShowFiltered */0 ? state._1 : [] + ); + var categories = Belt_Array.reduce(Js_dict.entries(Belt_Array.reduce(items, Js_dict.fromArray(initial), (function (acc, item) { + var key = toString(item.category); + return Belt_Option.mapWithDefault(Js_dict.get(acc, key), acc, (function (items) { + items.push(item); + acc[key] = items; + return acc; + })); + }))), [], (function (acc, entry) { + var items = entry[1]; + if (items.length === 0) { + return acc; + } + var title = entry[0]; + var children = Belt_Array.map(items, (function (item) { + var onMouseDown = function (evt) { + evt.preventDefault(); + return Curry._1(setState, (function (param) { + return { + TAG: 1, + _0: item, + [Symbol.for("name")]: "ShowDetails" + }; + })); + }; + return React.createElement("span", { + key: item.name, + className: "mr-2 mb-2 cursor-pointer", + onMouseDown: onMouseDown + }, React.createElement(SyntaxLookup$Tag, { + text: item.name + })); + })); + var el = React.createElement("div", { + key: title, + className: "first:mt-0 mt-12" + }, React.createElement(SyntaxLookup$Category, { + title: title, + children: children + })); + acc.push(el); + return acc; + })); + var match$1; + if (typeof state === "number") { + match$1 = [ + "", + allItems + ]; + } else if (state.TAG === /* ShowFiltered */0) { + match$1 = [ + state._0, + state._1 + ]; + } else { + var item$1 = state._0; + match$1 = [ + item$1.name, + [item$1] + ]; + } + var onSearchClear = function (param) { + return Curry._1(setState, (function (param) { + return /* ShowAll */0; + })); + }; + return React.createElement("div", undefined, React.createElement("div", { + className: "flex flex-col items-center" + }, React.createElement("div", { + className: "text-center", + style: { + maxWidth: "21rem" + } + }, React.createElement(Markdown.H1.make, { + children: "Syntax Lookup" + }), React.createElement("div", { + className: "mb-8 text-gray-60-tr text-14" + }, "Enter some language construct you want to know more about.")), React.createElement("div", { + className: "w-full", + style: { + maxWidth: "34rem" + } + }, React.createElement(SearchBox.make, { + completionValues: Belt_Array.map(match$1[1], (function (item) { + return item.name; + })), + value: match$1[0], + onClear: onSearchClear, + placeholder: "Enter keywords or syntax...", + onValueChange: onSearchValueChange + }))), React.createElement("div", { + className: "mt-10" + }, details, categories)); +} + +var make = SyntaxLookup; + +export { + make , + +} +/* indexData Not a pure module */ diff --git a/src/SyntaxLookup.res b/src/SyntaxLookup.res new file mode 100644 index 000000000..204be7be8 --- /dev/null +++ b/src/SyntaxLookup.res @@ -0,0 +1,311 @@ +// Structure defined by `scripts/extract-syntax.js` +type syntaxData = { + "file": string, + "id": string, + "keywords": array, + "name": string, + "summary": string, + "category": string, +} + +let indexData: array = %raw("require('index_data/syntax_index.json')") + +module MdxComp = { + type props + type t = Js.t => React.element + + let render: t => React.element = %raw(` + function(c) { + return React.createElement(c, {}); + } + `) + + /* @bs.get */ + /* external frontmatter: t => Js.Json.t = "frontmatter" */ +} + +let requireSyntaxFile: string => MdxComp.t = %raw(` + function(name) { + return require("misc_docs/syntax/" + name + ".mdx").default + } +`) + +module Category = { + type t = Decorators | ControlFlow | Operators | Other + + let toString = t => + switch t { + | Decorators => "Decorators" + | Operators => "Operators" + | ControlFlow => "Control Flow" + | Other => "Other" + } + + let fromString = (s: string): t => { + switch s { + | "decorators" => Decorators + | "controlflow" => ControlFlow + | "operators" => Operators + | _ => Other + } + } + + @react.component + let make = (~title, ~children) => { +
+

+ {React.string(title)} +

+
children
+
+ } +} + +// The data representing a syntax construct +// handled in the widget +type item = { + id: string, + keywords: array, + name: string, + summary: string, + category: Category.t, + component: MdxComp.t, +} + +let toItem = (syntaxData: syntaxData): item => { + let file = syntaxData["file"] + let id = syntaxData["id"] + let keywords = syntaxData["keywords"] + let name = syntaxData["name"] + let summary = syntaxData["summary"] + let category = syntaxData["category"] + let item: item = { + id: id, + keywords: keywords, + name: name, + summary: summary, + category: Category.fromString(category), + component: requireSyntaxFile(file), + } + item +} + +let allItems = indexData->Belt.Array.map(toItem) + +let fuseOpts = Fuse.Options.t( + ~shouldSort=false, + ~includeScore=true, + ~threshold=0.2, + ~location=0, + ~distance=30, + ~minMatchCharLength=1, + ~keys=["keywords", "name"], + (), +) + +let fuse: Fuse.t = Fuse.make(allItems, fuseOpts) + +let getAnchor = path => { + switch Js.String2.split(path, "#") { + | [_, anchor] => Some(anchor) + | _ => None + } +} + +module Tag = { + @react.component + let make = (~text: string) => { + + {React.string(text)} + + } +} + +module DetailBox = { + @react.component + let make = (~summary: string, ~children: React.element) => { + let summaryEl = switch Js.String2.split(summary, "`") { + | [] => React.null + | [first, second, third] => + [ + React.string(first), + {React.string(second)} , + React.string(third), + ] + ->Belt.Array.mapWithIndex((i, el) => { + el + }) + ->React.array + | more => Belt.Array.map(more, s => React.string(s))->React.array + } + +
+
summaryEl
+
children
+
+ } +} + +type state = + | ShowAll + | ShowFiltered(string, array) // (search, filteredItems) + | ShowDetails(item) + +@react.component +let make = () => { + let router = Next.Router.useRouter() + let (state, setState) = React.useState(_ => ShowAll) + + React.useEffect0(() => { + switch getAnchor(router.asPath) { + | Some(anchor) => + Js.Array2.find(allItems, item => + GithubSlugger.slug(item.id) === anchor + )->Belt.Option.forEach(item => { + setState(_ => ShowDetails(item)) + }) + | None => () + } + None + }) + + /* + This effect syncs the url anchor with the currently shown final match + within the fuzzy finder. If there is a new match that doesn't align with + the current anchor, the url will be rewritten with the correct anchor. + + In case a selection got removed, it will also remove the anchor from the url. + We don't replace on every state change, because replacing the url is expensive + */ + React.useEffect1(() => { + switch (state, getAnchor(router.asPath)) { + | (ShowDetails(item), Some(anchor)) => + let slug = GithubSlugger.slug(item.id) + + if slug !== anchor { + router->Next.Router.replace("syntax-lookup#" ++ anchor) + } else { + () + } + | (ShowDetails(item), None) => + router->Next.Router.replace("syntax-lookup#" ++ GithubSlugger.slug(item.id)) + | (_, Some(_)) => router->Next.Router.replace("syntax-lookup") + | _ => () + } + None + }, [state]) + + let onSearchValueChange = value => { + setState(_ => + switch value { + | "" => ShowAll + | search => + let filtered = + fuse + ->Fuse.search(search) + ->Belt.Array.map(m => { + m["item"] + }) + + if Js.Array.length(filtered) === 1 { + let item = Belt.Array.getExn(filtered, 0) + if item.name === value { + ShowDetails(item) + } else { + ShowFiltered(value, filtered) + } + } else { + ShowFiltered(value, filtered) + } + } + ) + } + + let details = switch state { + | ShowFiltered(_, _) + | ShowAll => React.null + | ShowDetails(item) => +
+ {MdxComp.render(item.component)} +
+ } + + /* + Order all items in tag groups + */ + let categories = { + open Category + let initial = [Decorators, Operators, ControlFlow, Other]->Belt.Array.map(cat => { + (cat->toString, []) + }) + + let items = switch state { + | ShowAll => allItems + | ShowDetails(_) => [] + | ShowFiltered(_, items) => items + } + + Belt.Array.reduce(items, Js.Dict.fromArray(initial), (acc, item) => { + let key = item.category->toString + Js.Dict.get(acc, key)->Belt.Option.mapWithDefault(acc, items => { + Js.Array2.push(items, item)->ignore + Js.Dict.set(acc, key, items) + acc + }) + }) + ->Js.Dict.entries + ->Belt.Array.reduce([], (acc, entry) => { + let (title, items) = entry + if Js.Array.length(items) === 0 { + acc + } else { + let children = Belt.Array.map(items, item => { + let onMouseDown = evt => { + ReactEvent.Mouse.preventDefault(evt) + setState(_ => ShowDetails(item)) + } + + + + }) + let el = +
+ {React.array(children)} +
+ Js.Array2.push(acc, el)->ignore + acc + } + }) + } + + let (searchValue, completionItems) = switch state { + | ShowFiltered(search, items) => (search, items) + | ShowAll => ("", allItems) + | ShowDetails(item) => (item.name, [item]) + } + + let onSearchClear = () => { + setState(_ => ShowAll) + } + +
+
+
+ {React.string("Syntax Lookup")} +
+ {React.string("Enter some language construct you want to know more about.")} +
+
+
+ item.name)} + value=searchValue + onClear=onSearchClear + onValueChange=onSearchValueChange + /> +
+
+
{details} {React.array(categories)}
+
+} diff --git a/src/SyntaxLookup.resi b/src/SyntaxLookup.resi new file mode 100644 index 000000000..1ca44ce26 --- /dev/null +++ b/src/SyntaxLookup.resi @@ -0,0 +1,2 @@ +@react.component +let make: unit => React.element diff --git a/src/bindings/GithubSlugger.js b/src/bindings/GithubSlugger.js new file mode 100644 index 000000000..d856702bf --- /dev/null +++ b/src/bindings/GithubSlugger.js @@ -0,0 +1,2 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE +/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ diff --git a/src/bindings/GithubSlugger.res b/src/bindings/GithubSlugger.res new file mode 100644 index 000000000..8faf4c6cb --- /dev/null +++ b/src/bindings/GithubSlugger.res @@ -0,0 +1 @@ +@module("github-slugger") external slug: string => string = "slug" diff --git a/src/components/SearchBox.js b/src/components/SearchBox.js new file mode 100644 index 000000000..1a82b11fd --- /dev/null +++ b/src/components/SearchBox.js @@ -0,0 +1,112 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Icon from "./Icon.js"; +import * as Curry from "bs-platform/lib/es6/curry.js"; +import * as React from "react"; +import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js"; +import * as Belt_Option from "bs-platform/lib/es6/belt_Option.js"; +import * as Caml_option from "bs-platform/lib/es6/caml_option.js"; + +function SearchBox(Props) { + var completionValuesOpt = Props.completionValues; + var value = Props.value; + var onClear = Props.onClear; + var placeholderOpt = Props.placeholder; + var onValueChange = Props.onValueChange; + var completionValues = completionValuesOpt !== undefined ? completionValuesOpt : []; + var placeholder = placeholderOpt !== undefined ? placeholderOpt : ""; + var match = React.useState(function () { + return /* Inactive */1; + }); + var setState = match[1]; + var state = match[0]; + var textInput = React.useRef(null); + var onMouseDownClear = function (evt) { + evt.preventDefault(); + return Curry._1(onClear, undefined); + }; + var onAreaFocus = function (evt) { + var el = evt.target; + var isDiv = (el.type == null); + if (isDiv && state === /* Inactive */1) { + return Belt_Option.forEach(Caml_option.nullable_to_opt(textInput.current), (function (el) { + el.focus(); + + })); + } + + }; + var onFocus = function (param) { + return Curry._1(setState, (function (param) { + return /* Active */0; + })); + }; + var onBlur = function (param) { + return Curry._1(setState, (function (param) { + return /* Inactive */1; + })); + }; + var onKeyDown = function (evt) { + var key = evt.key; + var ctrlKey = evt.ctrlKey; + var full = ( + ctrlKey ? "CTRL+" : "" + ) + key; + switch (full) { + case "Escape" : + return Curry._1(onClear, undefined); + case "Tab" : + if (completionValues.length !== 1) { + return ; + } + var targetValue = Belt_Array.getExn(completionValues, 0); + if (targetValue !== value) { + evt.preventDefault(); + return Curry._1(onValueChange, targetValue); + } else { + return ; + } + default: + return ; + } + }; + var onChange = function (evt) { + evt.preventDefault(); + return Curry._1(onValueChange, evt.target.value); + }; + return React.createElement("div", { + className: ( + state === /* Active */0 ? "border-fire" : "border-fire-40" + ) + " flex items-center border rounded-lg py-4 px-5", + tabIndex: -1, + onFocus: onAreaFocus, + onBlur: onBlur + }, React.createElement(Icon.MagnifierGlass.make, { + className: ( + state === /* Active */0 ? "text-fire" : "text-fire-80" + ) + " w-4 h-4" + }), React.createElement("input", { + ref: textInput, + className: "text-16 outline-none ml-4 w-full", + placeholder: placeholder, + type: "text", + value: value, + onKeyDown: onKeyDown, + onFocus: onFocus, + onChange: onChange + }), React.createElement("button", { + className: value === "" ? "hidden" : "block", + onFocus: onFocus, + onMouseDown: onMouseDownClear + }, React.createElement(Icon.Close.make, { + className: "w-4 h-4 text-fire" + }))); +} + +var make = SearchBox; + +export { + make , + +} +/* Icon Not a pure module */ diff --git a/src/components/SearchBox.res b/src/components/SearchBox.res new file mode 100644 index 000000000..8cd14de7e --- /dev/null +++ b/src/components/SearchBox.res @@ -0,0 +1,101 @@ +/* +* This SearchBox is used for fuzzy-find search scenarios, such as the syntax widget tool or +* the package index +*/ + +@bs.send external focus: Dom.element => unit = "focus" + +type state = + | Active + | Inactive + +@react.component +let make = ( + ~completionValues: array=[], // set of possible values + ~value: string, + ~onClear: unit => unit, + ~placeholder: string="", + ~onValueChange: string => unit, +) => { + let (state, setState) = React.useState(_ => Inactive) + let textInput = React.useRef(Js.Nullable.null) + + let onMouseDownClear = evt => { + ReactEvent.Mouse.preventDefault(evt) + onClear() + } + + let focusInput = () => + textInput.current->Js.Nullable.toOption->Belt.Option.forEach(el => el->focus) + + let onAreaFocus = evt => { + let el = ReactEvent.Focus.target(evt) + let isDiv = Js.Null_undefined.isNullable(el["type"]) + + if isDiv && state === Inactive { + focusInput() + } + } + + let onFocus = _ => { + setState(_ => Active) + } + + let onBlur = _ => { + setState(_ => Inactive) + } + + let onKeyDown = evt => { + let key = ReactEvent.Keyboard.key(evt) + let ctrlKey = ReactEvent.Keyboard.ctrlKey(evt) + + let full = (ctrlKey ? "CTRL+" : "") ++ key + + switch full { + | "Escape" => onClear() + | "Tab" => + if Js.Array.length(completionValues) === 1 { + let targetValue = Belt.Array.getExn(completionValues, 0) + + if targetValue !== value { + ReactEvent.Keyboard.preventDefault(evt) + onValueChange(targetValue) + } else { + () + } + } + | _ => () + } + } + + let onChange = evt => { + ReactEvent.Form.preventDefault(evt) + let value = ReactEvent.Form.target(evt)["value"] + onValueChange(value) + } + +
+ + + +
+} diff --git a/tailwind.config.js b/tailwind.config.js index ce14dc6b5..7dd07cfbd 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -95,7 +95,8 @@ module.exports = { "40": "rgba(230,72,79, 0.4)", "30": "#EDA7AA", "15": "rgba(230, 72, 79, 0.15)", - "10": "#FDE7E8" + "10": "#FDE7E8", + "10-tr": "rgba(230, 72, 79, 0.1)" }, sky: { default: "#376FDD",