Repository: blacksmithgu/obsidian-dataview (ts)

Issue:
Bug: replace doesn't replaceAll
### What happened?

I'm currently debugging through `dv.sectionLink` and came across this:
![image](https://github.com/blacksmithgu/obsidian-dataview/assets/559564/aa7a3283-6c53-4ff7-9d0e-f244831af80e)

if you run `"|||".replace("|", "\\|")` in your console, you will get this: `\|||`
to get the correct output, `replaceAll` should be used instead.

I'll create a pull request for this immediately.

### DQL

_No response_

### JS

_No response_

### Dataview Version

0.5.64

### Obsidian Version

v1.5.8

### OS

Windows

Pull request description:
Fix: properly escaping pipes in Link.obsidianLink()
Closes #2264

please tell me if this is desired behavior or not.

edit:

```
TS2550: Property 'replaceAll' does not exist on type 'string'. Do you need to change your target library? Try changing the 'lib' compiler option to 'es2021' or later.
```

so.. are you willing to go that step? adding this change is more complicated than replacing it with `replace(/\|/g, "\\|")`

Patch:
diff --git a/docs/docs/reference/functions.md b/docs/docs/reference/functions.md
index 2bfd5d4..310f880 100644
--- a/docs/docs/reference/functions.md
+++ b/docs/docs/reference/functions.md
@@ -519,6 +519,18 @@ flat(list(1, list(21, 22), list(list (311, 312, 313))), 4) => list(1, 21, 22, 31
 flat(rows.file.outlinks)) => All the file outlinks at first level in output
 ```
 
+### `slice(array, [start, [end]])`
+
+Returns a shallow copy of a portion of an array into a new array object selected from `start`
+to `end` (`end` not included) where `start` and `end` represents the index of items in that array.
+
+```js
+slice([1, 2, 3, 4, 5], 3) = [4, 5] => All items from given position, 0 as first
+slice(["ant", "bison", "camel", "duck", "elephant"], 0, 2) = ["ant", "bison"] => First two items 
+slice([1, 2, 3, 4, 5], -2) = [4, 5] => counts from the end, last two items
+slice(someArray) => a copy of someArray
+```
+
 ---
 
 ## String Operations
diff --git a/src/data-model/value.ts b/src/data-model/value.ts
index bff91e2..834c54a 100644
--- a/src/data-model/value.ts
+++ b/src/data-model/value.ts
@@ -554,9 +554,9 @@ export class Link {
 
     /** Convert the inner part of the link to something that Obsidian can open / understand. */
     public obsidianLink(): string {
-        const escaped = this.path.replace("|", "\\|");
-        if (this.type == "header") return escaped + "#" + this.subpath?.replace("|", "\\|");
-        if (this.type == "block") return escaped + "#^" + this.subpath?.replace("|", "\\|");
+        const escaped = this.path.replaceAll("|", "\\|");
+        if (this.type == "header") return escaped + "#" + this.subpath?.replaceAll("|", "\\|");
+        if (this.type == "block") return escaped + "#^" + this.subpath?.replaceAll("|", "\\|");
         else return escaped;
     }
 
diff --git a/src/expression/functions.ts b/src/expression/functions.ts
index a76390a..7bd5c5d 100644
--- a/src/expression/functions.ts
+++ b/src/expression/functions.ts
@@ -831,6 +831,20 @@ export namespace DefaultFunctions {
         })
         .add1("null", () => null)
         .build();
+
+    // Slices the array into a new array
+    export const slice = new FunctionBuilder("slice")
+        .add1("array", a => {
+            return a.slice();
+        })
+        .add2("array", "number", (a, start) => {
+            return a.slice(start);
+        })
+        .add3("array", "number", "number", (a, start, end) => {
+            return a.slice(start, end);
+        })
+        .add1("null", () => null)
+        .build();
 }
 
 /** Default function implementations for the expression evaluator. */
@@ -889,6 +903,7 @@ export const DEFAULT_FUNCTIONS: Record<string, FunctionImpl> = {
     reverse: DefaultFunctions.reverse,
     sort: DefaultFunctions.sort,
     flat: DefaultFunctions.flat,
+    slice: DefaultFunctions.slice,
 
     // Aggregation operations like reduce.
     reduce: DefaultFunctions.reduce,
diff --git a/src/main.ts b/src/main.ts
index bafde60..f42e370 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -7,6 +7,7 @@ import {
     Plugin,
     PluginSettingTab,
     Setting,
+    WorkspaceLeaf,
 } from "obsidian";
 import { renderErrorPre } from "ui/render";
 import { FullIndex } from "data-index/index";
@@ -122,6 +123,21 @@ export default class DataviewPlugin extends Plugin {
             },
         });
 
+        interface WorkspaceLeafRebuild extends WorkspaceLeaf {
+            rebuildView(): void;
+        }
+
+        this.addCommand({
+            id: "dataview-rebuild-current-view",
+            name: "Rebuild current view",
+            callback: () => {
+                const activeView: MarkdownView | null = this.app.workspace.getActiveViewOfType(MarkdownView);
+                if (activeView) {
+                    (activeView.leaf as WorkspaceLeafRebuild).rebuildView();
+                }
+            },
+        });
+
         // Run index initialization, which actually traverses the vault to index files.
         if (!this.app.workspace.layoutReady) {
             this.app.workspace.onLayoutReady(async () => this.index.initialize());
diff --git a/tsconfig.json b/tsconfig.json
index 075e2cc..e05233e 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -5,7 +5,7 @@
     "inlineSourceMap": true,
     "inlineSources": true,
     "module": "ESNext",
-    "target": "es2018",
+    "target": "ES2022",
     "allowJs": false,
     "jsx": "react",
     "jsxFactory": "h",
@@ -19,7 +19,7 @@
     "importHelpers": true,
     "downlevelIteration": true,
     "esModuleInterop": true,
-    "lib": ["dom", "es5", "scripthost", "es2018", "DOM.Iterable"]
+    "lib": ["dom", "scripthost", "ES2022"]
   },
   "include": ["src/**/*.ts", "src/**/*.tsx"],
   "exclude": ["lib/**/*"]


Test patch:
diff --git a/src/test/function/functions.test.ts b/src/test/function/functions.test.ts
index 72595c6..fc35851 100644
--- a/src/test/function/functions.test.ts
+++ b/src/test/function/functions.test.ts
@@ -72,6 +72,25 @@ test("Evaluate flat()", () => {
     expect(parseEval("flat(list(1, list(2, list(3, list(4, list(5))))), 10)")).toEqual(parseEval("list(1,2,3,4,5)")); // flat(..., 10)
 });
 
+// <-- slice() -->
+
+test("Evaluate slice()", () => {
+    expect(parseEval("slice(list(1, 2, 3, 4, 5), 3)")).toEqual(parseEval("list(4, 5)")); // slice(..., 3)
+    expect(parseEval("slice(list(1, 2, 3, 4, 5), 0, 2)")).toEqual(parseEval("list(1, 2)")); // slice(..., 0, 2)
+    expect(parseEval("slice(list(1, 2, 3, 4, 5), -2)")).toEqual(parseEval("list(4, 5)")); // slice(..., -2)
+    expect(parseEval("slice(list(1, 2, 3, 4, 5), -1, 1)")).toEqual(parseEval("list()")); // slice(..., -1, 1)
+    expect(parseEval("slice(list(1, 2, 3, 4, 5))")).toEqual(parseEval("list(1, 2, 3, 4, 5)")); // slice(...)
+    expect(parseEval('slice(list(date("2021-01-01"), date("2022-02-02"), date("2023-03-03")), -2)')).toEqual([
+        DateTime.fromObject({ year: 2022, month: 2, day: 2 }),
+        DateTime.fromObject({ year: 2023, month: 3, day: 3 }),
+    ]); // slice(date list, -2)
+    expect(parseEval('slice(["ant", "bison", "camel", "duck", "elephant"], -3)')).toEqual([
+        "camel",
+        "duck",
+        "elephant",
+    ]); // slice(string list, -3)
+});
+
 // <-- sort() -->
 
 describe("sort()", () => {