Repository: blacksmithgu/obsidian-dataview (ts)

Issue:
dv.view("folder path") doesn't attach CSS to the top
### What happened?

the css is appended to the bottom of the container.
![image](https://github.com/blacksmithgu/obsidian-dataview/assets/559564/20ed2176-c4c8-44de-beb7-9772f0b28706)
https://github.com/blacksmithgu/obsidian-dataview/blob/e4a6cab97b628deb22d36b73ce912abca541ad42/src/api/inline-api.ts#L317-L368

it should be attached to the top, to prevent triggering epileptic seizures / visual jumping. when refreshing a view that is affected.

you can toy with that effect by creating a button that triggers `app.commands.executeCommandById("dataview:dataview-force-refresh-views")`

### Dataview Version

0.5.64

### Obsidian Version

v1.5.8

### OS

Windows

Pull request:
Fix: Moving CSS embedding to the top of the view container
closes #2260

tested with npm build and by using it in obsidian.

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/api/inline-api.ts b/src/api/inline-api.ts
index e0c991f..79753d0 100644
--- a/src/api/inline-api.ts
+++ b/src/api/inline-api.ts
@@ -319,6 +319,7 @@ export class DataviewInlineApi {
         const simpleViewPath = `${viewName}.js`;
         const complexViewPath = `${viewName}/view.js`;
         let checkForCss = false;
+        let cssElement = undefined;
         let viewFile = this.app.metadataCache.getFirstLinkpathDest(simpleViewPath, this.currentFilePath);
         if (!viewFile) {
             viewFile = this.app.metadataCache.getFirstLinkpathDest(complexViewPath, this.currentFilePath);
@@ -333,6 +334,16 @@ export class DataviewInlineApi {
             return;
         }
 
+        if (checkForCss) {
+            // Check for optional CSS.
+            let cssFile = this.app.metadataCache.getFirstLinkpathDest(`${viewName}/view.css`, this.currentFilePath);
+            if (cssFile) {
+                let cssContents = await this.app.vault.read(cssFile);
+                cssContents += `\n/*# sourceURL=${location.origin}/${cssFile.path} */`;
+                cssElement = this.container.createEl("style", { text: cssContents, attr: { scope: " " } });
+            }
+        }
+
         let contents = await this.app.vault.read(viewFile);
         if (contents.contains("await")) contents = "(async () => { " + contents + " })()";
         contents += `\n//# sourceURL=${viewFile.path}`;
@@ -352,20 +363,9 @@ export class DataviewInlineApi {
                     true
                 );
         } catch (ex) {
+            if (cssElement) this.container.removeChild(cssElement);
             renderErrorPre(this.container, `Dataview: Failed to execute view '${viewFile.path}'.\n\n${ex}`);
         }
-
-        if (!checkForCss) {
-            return;
-        }
-
-        // Check for optional CSS.
-        let cssFile = this.app.metadataCache.getFirstLinkpathDest(`${viewName}/view.css`, this.currentFilePath);
-        if (!cssFile) return;
-
-        let cssContents = await this.app.vault.read(cssFile);
-        cssContents += `\n/*# sourceURL=${location.origin}/${cssFile.path} */`;
-        this.container.createEl("style", { text: cssContents, attr: { scope: " " } });
     }
 
     /** Render a dataview list of the given values. */
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()", () => {