diff --git a/AGENTS.md b/AGENTS.md index 8e3bfbd4f..00cfb2840 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -76,7 +76,7 @@ Full author docs are in [CONTRIBUTING.md](CONTRIBUTING.md). Other conventions: - Callouts use `> :note:`, `> :tip:`, `> :info:` (custom syntax, not GitHub's `[!NOTE]` alerts). -- Step navigation footer: `***` separator, then `**Next:** [Step N+1: ](../<NN+1>/README.md)` and `**Previous:** [Step N-1: <title>](../<NN-1>/README.md)`. +- Step navigation footer: a single `***` separator, then `**Next:** [Step N+1: <title>](../<NN+1>/README.md)` and `**Previous:** [Step N-1: <title>](../<NN-1>/README.md)`. When a step also has a **Related Information** section, the order is Next/Previous first, then another `***`, then `**Related Information**` (matches all five tutorials). The first step has no `**Previous:**`; the last step has no `**Next:**`. The link display text must match the target step's actual title. - Preview images: descriptive `alt` text (no bare `![](…)`), optional `"title"` for the hover tooltip. - Do not add `<!-- loio… -->` markers or `<a name="loio…"/>` anchors — those are SAP-internal SDK artifacts. diff --git a/assets/css/custom.css b/assets/css/custom.css index e94d22b53..c0049a2ef 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -165,4 +165,36 @@ li > section.ts-only, li > section.js-only { #__bs_notify__ { background-color: rgba(27, 32, 50, 0.3) !important; top: 40px !important; +} + +/* Image captions rendered from each image's alt text (see renderImageCaptions + in assets/js/custom.js). Restores the SAP Demo Kit behavior of showing the + image description below the figure. */ +figure.img-figure { + margin: 16px 0; +} + +figure.img-figure > .img-caption { + margin-top: 6px; + font-size: 90%; + font-style: italic; + color: var(--inactive-color); + text-align: center; +} + +/* Breadcrumb shown at the top of each step page: "UI5 Tutorials › <Tutorial>". + Injected server-side (tools/builder/prepare-gh-pages.js for GitHub Pages, + tools/dev-server/server.js for local preview) so the reader knows which + tutorial they are in and can jump back to its overview. */ +.tutorial-breadcrumb { + margin-bottom: 16px; + padding-bottom: 8px; + border-bottom: 1px solid var(--neutral-border); + font-size: 90%; + color: var(--inactive-color); +} + +.tutorial-breadcrumb .tutorial-breadcrumb-sep { + margin: 0 6px; + color: var(--inactive-color); } \ No newline at end of file diff --git a/assets/js/custom.js b/assets/js/custom.js index b9ac21b4e..337e3e174 100644 --- a/assets/js/custom.js +++ b/assets/js/custom.js @@ -276,6 +276,60 @@ function updateAllCodeCouples(globalLang) { // initialization on startup +/** + * Render each content image's `alt` text as a visible caption beneath it, + * mirroring the SAP Demo Kit behavior that was lost when the tutorials moved + * to GitHub Pages. The caption text lives only in the image's `alt` attribute + * (also used by screen readers and as the broken-image fallback), so there is + * a single source of truth. + * + * Idempotent: an image already wrapped in <figure class="img-figure"> is + * skipped, so re-running (e.g. after a language switch) does not duplicate. + */ +function renderImageCaptions() { + const images = document.querySelectorAll("img"); + images.forEach((img) => { + const caption = (img.getAttribute("alt") || "").trim(); + if (!caption) { + return; // no alt text -> no caption + } + if (img.closest("figure.img-figure")) { + return; // already processed + } + + // A link-wrapped image is captioned via its anchor; otherwise the image + // itself is the wrap target. + const wrapTarget = + img.parentElement && img.parentElement.tagName === "A" + ? img.parentElement + : img; + const host = wrapTarget.parentElement; + if (!host) { + return; + } + + // Only caption block-level "preview" images — those that are the sole + // meaningful content of their containing block. This skips inline + // images sitting inside a line of prose (badges, inline icons), which + // should not get a centered caption. `host.textContent` is empty for a + // standalone image because the <img> contributes no text. + if (host.textContent.trim() !== "") { + return; + } + + const figure = document.createElement("figure"); + figure.classList.add("img-figure"); + + const figcaption = document.createElement("figcaption"); + figcaption.classList.add("img-caption"); + figcaption.textContent = caption; + + wrapTarget.parentNode.insertBefore(figure, wrapTarget); + figure.appendChild(wrapTarget); + figure.appendChild(figcaption); + }); +} + document.addEventListener("DOMContentLoaded", (event) => { setTimeout(() => { const lang = initializeLanguage(); @@ -285,5 +339,6 @@ document.addEventListener("DOMContentLoaded", (event) => { addLanguageSwitchButtons(); updateAllCodeCouples(lang); updatePrevNextLinks(lang); + renderImageCaptions(); }); }); diff --git a/packages/navigation/steps/01/README.md b/packages/navigation/steps/01/README.md index 104a0224d..4d2b5d4d6 100644 --- a/packages/navigation/steps/01/README.md +++ b/packages/navigation/steps/01/README.md @@ -89,9 +89,6 @@ With the downloaded coding, you have an initial app with recommended settings th So far we have a basic app that does not really have any navigation or routing implemented. This will change in the next steps when we implement our first navigation features. -*** - - *** **Next:** [Step 2: Enable Routing](../02/README.md) diff --git a/packages/navigation/steps/02/README.md b/packages/navigation/steps/02/README.md index 224db8b27..0bebf721b 100644 --- a/packages/navigation/steps/02/README.md +++ b/packages/navigation/steps/02/README.md @@ -238,8 +238,6 @@ Create a file `Home.controller.ts` in the `webapp/controller` folder. The contro *** -*** - **Next:** [Step 3: Catch Invalid Hashes](../03/README.md) **Previous:** [Step 1: Set Up the Initial App](../01/README.md) diff --git a/packages/navigation/steps/03/README.md b/packages/navigation/steps/03/README.md index 189cdf197..43572f389 100644 --- a/packages/navigation/steps/03/README.md +++ b/packages/navigation/steps/03/README.md @@ -160,8 +160,6 @@ Open the URL `index.html#/thisIsInvalid` in your browser. From now on the user w *** -*** - **Next:** [Step 4: Add a *Back* Button to *Not Found* Page](../04/README.md) **Previous:** [Step 2: Enable Routing](../02/README.md) diff --git a/packages/navigation/steps/04/README.md b/packages/navigation/steps/04/README.md index 9d29b1137..1673c5408 100644 --- a/packages/navigation/steps/04/README.md +++ b/packages/navigation/steps/04/README.md @@ -231,8 +231,6 @@ The same applies to our `Home` controller, we now also inherit from the `BaseCon *** -*** - **Next:** [Step 5: Display a Target Without Changing the Hash](../05/README.md) **Previous:** [Step 3: Catch Invalid Hashes](../03/README.md) diff --git a/packages/navigation/steps/05/README.md b/packages/navigation/steps/05/README.md index 13e545fe3..a923d2129 100644 --- a/packages/navigation/steps/05/README.md +++ b/packages/navigation/steps/05/README.md @@ -218,8 +218,6 @@ When we now click the *Back* button, it works as expected and brings us back to *** -*** - **Next:** [Step 6: Navigate to Routes with Hard-Coded Patterns](../06/README.md) **Previous:** [Step 4: Add a *Back* Button to *Not Found* Page](../04/README.md) diff --git a/packages/navigation/steps/06/README.md b/packages/navigation/steps/06/README.md index ad2afa026..b4c203968 100644 --- a/packages/navigation/steps/06/README.md +++ b/packages/navigation/steps/06/README.md @@ -265,8 +265,6 @@ Now you can open the app and press the *Show Employee List* button to navigate t *** -*** - **Next:** [Step 7: Navigate to Routes with Mandatory Parameters](../07/README.md) **Previous:** [Step 5: Display a Target Without Changing the Hash](../05/README.md) diff --git a/packages/navigation/steps/07/README.md b/packages/navigation/steps/07/README.md index d37cc4c0e..365a2ddf7 100644 --- a/packages/navigation/steps/07/README.md +++ b/packages/navigation/steps/07/README.md @@ -394,8 +394,6 @@ That’s it. You can go to `webapp/index.html#/employees` and click on any list *** -*** - **Next:** [Step 8: Navigate with Flip Transition](../08/README.md) **Previous:** [Step 6: Navigate to Routes with Hard-Coded Patterns](../06/README.md) diff --git a/packages/navigation/steps/08/README.md b/packages/navigation/steps/08/README.md index 5037474fd..242ebccaf 100644 --- a/packages/navigation/steps/08/README.md +++ b/packages/navigation/steps/08/README.md @@ -429,8 +429,6 @@ You can go to `webapp/index.html#/employees/3` and click on the *Flip to Resume* *** -*** - **Next:** [Step 9: Allow Bookmarkable Tabs with Optional Query Parameters](../09/README.md) **Previous:** [Step 7: Navigate to Routes with Mandatory Parameters](../07/README.md) diff --git a/packages/navigation/steps/09/README.md b/packages/navigation/steps/09/README.md index 46847eb44..cfe805b38 100644 --- a/packages/navigation/steps/09/README.md +++ b/packages/navigation/steps/09/README.md @@ -288,8 +288,6 @@ When you click on any tab you will see that the hash in the URL changes immediat *** -*** - **Next:** [Step 10: Implement “Lazy Loading”](../10/README.md) **Previous:** [Step 8: Navigate with Flip Transition](../08/README.md) diff --git a/packages/navigation/steps/10/README.md b/packages/navigation/steps/10/README.md index 8de191475..2a0d74671 100644 --- a/packages/navigation/steps/10/README.md +++ b/packages/navigation/steps/10/README.md @@ -296,8 +296,6 @@ Try it out yourself: Open the *Network* tab of your browser's developer tools an *** -*** - **Next:** [Step 11: Assign Multiple Targets](../11/README.md) **Previous:** [Step 9: Allow Bookmarkable Tabs with Optional Query Parameters](../09/README.md) diff --git a/packages/navigation/steps/11/README.md b/packages/navigation/steps/11/README.md index bccc38485..bcf0fb8f5 100644 --- a/packages/navigation/steps/11/README.md +++ b/packages/navigation/steps/11/README.md @@ -567,8 +567,6 @@ Add the new texts to the `properties` file. *** -*** - **Next:** [Step 12: Make a Search Bookmarkable](../12/README.md) **Previous:** [Step 10: Implement “Lazy Loading”](../10/README.md) diff --git a/packages/navigation/steps/12/README.md b/packages/navigation/steps/12/README.md index 967b67c15..c0536093f 100644 --- a/packages/navigation/steps/12/README.md +++ b/packages/navigation/steps/12/README.md @@ -273,8 +273,6 @@ When you change the value in the search field, you see that the hash updates acc *** -*** - **Next:** [Step 13: Make Table Sorting Bookmarkable](../13/README.md) **Previous:** [Step 11: Assign Multiple Targets](../11/README.md) diff --git a/packages/navigation/steps/13/README.md b/packages/navigation/steps/13/README.md index c9b1202dc..49d22e2b3 100644 --- a/packages/navigation/steps/13/README.md +++ b/packages/navigation/steps/13/README.md @@ -211,8 +211,6 @@ When changing the table’s sorting options, you will see that the hash updates *** -*** - **Next:** [Step 14: Make Dialogs Bookmarkable](../14/README.md) **Previous:** [Step 12: Make a Search Bookmarkable](../12/README.md) diff --git a/packages/navigation/steps/14/README.md b/packages/navigation/steps/14/README.md index 1949822df..84ec50939 100644 --- a/packages/navigation/steps/14/README.md +++ b/packages/navigation/steps/14/README.md @@ -239,8 +239,6 @@ As you can see, the dialog opens automatically if the parameter `showDialog=1` i *** -*** - **Next:** [Step 15: Reuse an Existing Route](../15/README.md) **Previous:** [Step 13: Make Table Sorting Bookmarkable](../13/README.md) diff --git a/packages/navigation/steps/15/README.md b/packages/navigation/steps/15/README.md index 0dbb90a84..70c5047ac 100644 --- a/packages/navigation/steps/15/README.md +++ b/packages/navigation/steps/15/README.md @@ -236,8 +236,6 @@ Next we add the `itemPress` handler `.onItemPressed` to the `EmployeeOverviewCon *** -*** - **Next:** [Step 16: Handle Invalid Hashes by Listening to Bypassed Events](../16/README.md) **Previous:** [Step 14: Make Dialogs Bookmarkable](../14/README.md) diff --git a/packages/navigation/steps/16/README.md b/packages/navigation/steps/16/README.md index 9c8a27738..c04958c14 100644 --- a/packages/navigation/steps/16/README.md +++ b/packages/navigation/steps/16/README.md @@ -82,8 +82,6 @@ Now try to access `webapp/index.html#/thisIsInvalid` while you have your browser *** -*** - **Next:** [Step 17: Listen to Matched Events of Any Route](../17/README.md) **Previous:** [Step 15: Reuse an Existing Route](../15/README.md) diff --git a/packages/odatav4/steps/01/README.md b/packages/odatav4/steps/01/README.md index 52f916360..7e702ec89 100644 --- a/packages/odatav4/steps/01/README.md +++ b/packages/odatav4/steps/01/README.md @@ -93,6 +93,12 @@ To be able to add data to the emulated OData responses, we have to store the ent In this tutorial, we only use the entity type `Person` of the *TripPin* service. The entities of type `Person` are collected in the entity set `People`. Each `Person` has a key property `UserName` and the properties `Age`, `FirstName`, and `LastName`. +*** + +**Next:** [Step 2: Data Access and Client-Server Communication](../02/README.md) + +*** + **Related Information** [*OData* Reference Services including *TripPin*](http://www.odata.org/odata-services/) @@ -100,7 +106,3 @@ In this tutorial, we only use the entity type `Person` of the *TripPin* service. [Bootstrapping: Loading and Initializing](https://sdk.openui5.org/topic/a04b0d10fb494d1cb722b9e341b584ba.html "To use OpenUI5 features in your HTML page, you have to load and initialize the OpenUI5 library.") [Descriptor for Applications, Components, and Libraries \(manifest.json\)](https://sdk.openui5.org/topic/be0cf40f61184b358b5faedaec98b2da.html "The descriptor for applications, components, and libraries (in short: app descriptor) is inspired by the WebApplication Manifest concept introduced by the W3C. The descriptor provides a central, machine-readable, and easy-to-access location for storing metadata associated with an application, an application component, or a library.") - -*** - -**Next:** [Step 2: Data Access and Client-Server Communication](../02/README.md) diff --git a/packages/odatav4/steps/02/README.md b/packages/odatav4/steps/02/README.md index a08e33e55..1eecb4602 100644 --- a/packages/odatav4/steps/02/README.md +++ b/packages/odatav4/steps/02/README.md @@ -6,9 +6,7 @@ In this step, we see how the `Table` that is bound to the `People` entity set in **App with a toolbar that contains a Refresh button** -![App with a toolbar that contains a Refresh - button](assets/Tutorial_OData_V4_Step_2_0abcbb6.png "App with a toolbar that contains a Refresh - button") +![App with a toolbar that contains a Refresh button](assets/Tutorial_OData_V4_Step_2_0abcbb6.png "App with a toolbar that contains a Refresh button") You can view this step live: [🔗 Live Preview of Step 2](https://ui5.github.io/tutorials/odatav4/build/02/index-cdn.html). @@ -225,6 +223,14 @@ We search for the following mock server requests: This request explicitly lists the fields that should be included in the response by using the `$select` query option. Although the *TripPin* service has more fields in its `People` entity set, only those four are included in the response. This is a feature of the OData V4 Model called "automatic determination of `$select`", or "auto-`$select`". It helps restricting the size of responses to what is really needed. The `ODataModel` computes the required fields from binding paths specified for controls. This feature is not active by default. In our case, this is activated by setting the `autoExpandSelect` property to `true` when instantiating the model in the `manifest.json` descriptor file . +*** + +**Next:** [Step 3: Automatic Data Type Detection](../03/README.md) + +**Previous:** [Step 1: The Initial App](../01/README.md) + +*** + **Related Information** [Bindings](https://sdk.openui5.org/topic/54e0ddf695af4a6c978472cecb01c64d "Bindings connect OpenUI5 view elements to model data, allowing changes in the model to be reflected in the view element and vice versa.") @@ -234,9 +240,3 @@ We search for the following mock server requests: [API Reference: `sap.ui.model.odata.v4.ODataListBinding.refresh`](https://sdk.openui5.org/#/api/sap.ui.model.odata.v4.ODataListBinding/methods/refresh) [Troubleshooting Tutorial Step 1: Browser Developer Tools](step-1-browser-developer-tools-eadd60a.md "In this step, you will learn how to use your browser's developers tools to troubleshoot your OpenUI5 app.") - -*** - -**Next:** [Step 3: Automatic Data Type Detection](../03/README.md) - -**Previous:** [Step 1: The Initial App](../01/README.md) diff --git a/packages/odatav4/steps/03/README.md b/packages/odatav4/steps/03/README.md index d9d14bd40..f7b12c34e 100644 --- a/packages/odatav4/steps/03/README.md +++ b/packages/odatav4/steps/03/README.md @@ -80,6 +80,14 @@ To make the *User Name* optional, we remove the parameter `Nullable="false"` fro > 💡 > To see the metadata of an OData service, you append the `$metadata` variable to the URL of the service. You can try this, for example, with [http://services.odata.org/TripPinRESTierService/](http://services.odata.org/TripPinRESTierService/) and [http://services.odata.org/TripPinRESTierService/$metadata](http://services.odata.org/TripPinRESTierService/$metadata) +*** + +**Next:** [Step 4: Filtering, Sorting, and Counting](../04/README.md) + +**Previous:** [Step 2: Data Access and Client-Server Communication](../02/README.md) + +*** + **Related Information** [Type Determination](https://sdk.openui5.org/topic/53cdd55a77ce4f33a14bd0767a293063 "") @@ -87,9 +95,3 @@ To make the *User Name* optional, we remove the parameter `Nullable="false"` fro [API Reference: `sap.ui.model.odata.type`](https://sdk.openui5.org/#/api/sap.ui.model.odata.type) [Sample for `sap.ui.core.mvc.XMLView`: *XML Templating: UI5 OData types*](https://sdk.openui5.org/#/entity/sap.ui.core.mvc.XMLView/sample/sap.ui.core.sample.ViewTemplate.types) - -*** - -**Next:** [Step 4: Filtering, Sorting, and Counting](../04/README.md) - -**Previous:** [Step 2: Data Access and Client-Server Communication](../02/README.md) diff --git a/packages/odatav4/steps/04/README.md b/packages/odatav4/steps/04/README.md index 07231df87..771c488c2 100644 --- a/packages/odatav4/steps/04/README.md +++ b/packages/odatav4/steps/04/README.md @@ -6,9 +6,7 @@ In this step, we add features to filter, sort, and count the user data by using **App now has a search field, the entries can be sorted, and you can see how many entities are loaded and how many more are available** -![App now has a search field, the entries can be sorted, and you can see how - many entities are loaded and how many more are available](assets/Tutorial_OData_V4_Step_4_3ac4fcc.png "App now has a search field, the entries can be sorted, and you can see how - many entities are loaded and how many more are available") +![App now has a search field, the entries can be sorted, and you can see how many entities are loaded and how many more are available](assets/Tutorial_OData_V4_Step_4_3ac4fcc.png "App now has a search field, the entries can be sorted, and you can see how many entities are loaded and how many more are available") You can view this step live: [🔗 Live Preview of Step 4](https://ui5.github.io/tutorials/odatav4/build/04/index-cdn.html). @@ -277,6 +275,14 @@ sortNone=the sequence on the server We add the missing texts to the properties file. +*** + +**Next:** [Step 5: Batch Groups](../05/README.md) + +**Previous:** [Step 3: Automatic Data Type Detection](../03/README.md) + +*** + **Related Information** [Filtering](https://sdk.openui5.org/topic/5338bd1f9afb45fb8b2af957c3530e8f "The OData V4 Model supports server side filtering on lists.") @@ -284,9 +290,3 @@ We add the missing texts to the properties file. [Sorting](https://sdk.openui5.org/topic/d2ce3f51e5e34198b0c1a7f6ddd98def "The OData V4 model supports server side sorting on lists.") [Query Options under *Querying Data* in the Basic Tutorial on the OData home page](http://www.odata.org/getting-started/basic-tutorial/#queryData) - -*** - -**Next:** [Step 5: Batch Groups](../05/README.md) - -**Previous:** [Step 3: Automatic Data Type Detection](../03/README.md) diff --git a/packages/odatav4/steps/05/README.md b/packages/odatav4/steps/05/README.md index dec5f1d7d..6c73a57c8 100644 --- a/packages/odatav4/steps/05/README.md +++ b/packages/odatav4/steps/05/README.md @@ -43,14 +43,16 @@ A `$batch` request uses multipart MIME to put several requests into one. This ma - Copy the relevant part of the request or response from the developer tools to an editor and auto-format it as JSON to analyze it. -**Related Information** +*** -[Batch Control](https://sdk.openui5.org/topic/74142a38e3d4467c8d6a70b28764048f "OData V4 allows you to group multiple operations into a single HTTP request payload, as described in the official OData V4 specification Part 1, Batch Requests (see the link under Related Information for more details).") +**Next:** [Step 6: Create and Edit](../06/README.md) -[Performance Aspects](https://sdk.openui5.org/topic/5a0d286c5606424b8e0d663c87445733 "The OData V4 model offers the features described below which influence performance.") +**Previous:** [Step 4: Filtering, Sorting, and Counting](../04/README.md) *** -**Next:** [Step 6: Create and Edit](../06/README.md) +**Related Information** -**Previous:** [Step 4: Filtering, Sorting, and Counting](../04/README.md) +[Batch Control](https://sdk.openui5.org/topic/74142a38e3d4467c8d6a70b28764048f "OData V4 allows you to group multiple operations into a single HTTP request payload, as described in the official OData V4 specification Part 1, Batch Requests (see the link under Related Information for more details).") + +[Performance Aspects](https://sdk.openui5.org/topic/5a0d286c5606424b8e0d663c87445733 "The OData V4 model offers the features described below which influence performance.") diff --git a/packages/odatav4/steps/05/assets/Tutorial_OData_V4_Step_4_3ac4fcc.png b/packages/odatav4/steps/05/assets/Tutorial_OData_V4_Step_4_3ac4fcc.png new file mode 100644 index 000000000..baaba22c5 Binary files /dev/null and b/packages/odatav4/steps/05/assets/Tutorial_OData_V4_Step_4_3ac4fcc.png differ diff --git a/packages/odatav4/steps/06/README.md b/packages/odatav4/steps/06/README.md index af8f75dfe..1911733e1 100644 --- a/packages/odatav4/steps/06/README.md +++ b/packages/odatav4/steps/06/README.md @@ -570,6 +570,14 @@ changesSentMessage=User data sent to the server We add the new message texts. +*** + +**Next:** [Step 7: Delete](../07/README.md) + +**Previous:** [Step 5: Batch Groups](../05/README.md) + +*** + **Related Information** [Model Instantiation and Data Access](https://sdk.openui5.org/topic/9613f1f2d88747cab21896f7216afdac "One OData V4 model instance can only cover one OData service. This section describes the creation of a model instance in more detail.") @@ -583,9 +591,3 @@ We add the new message texts. [Message Model](https://sdk.openui5.org/topic/8956f0a223284d729900ebad4ca88356 "The message model contains all messages and is used to bind to the messages to display them.") [API Reference: `sap.ui.model.odata.v4.ODataContextBinding`](https://sdk.openui5.org/#/api/sap.ui.model.odata.v4.ODataContextBinding) - -*** - -**Next:** [Step 7: Delete](../07/README.md) - -**Previous:** [Step 5: Batch Groups](../05/README.md) diff --git a/packages/odatav4/steps/07/README.md b/packages/odatav4/steps/07/README.md index 5946e32ba..4d682c00c 100644 --- a/packages/odatav4/steps/07/README.md +++ b/packages/odatav4/steps/07/README.md @@ -180,12 +180,14 @@ deletionRestoredMessage=User {0} restored We add the missing texts to the properties file. -**Related Information** - -[Deleting an Entity](https://sdk.openui5.org/topic/2613ebc835764abd9aefd2e6fa8b7392 "The v4.Context.delete method deletes an entity on the server and updates the user interface accordingly.") - *** **Next:** [Step 8: OData Operations](../08/README.md) **Previous:** [Step 6: Create and Edit](../06/README.md) + +*** + +**Related Information** + +[Deleting an Entity](https://sdk.openui5.org/topic/2613ebc835764abd9aefd2e6fa8b7392 "The v4.Context.delete method deletes an entity on the server and updates the user interface accordingly.") diff --git a/packages/odatav4/steps/08/README.md b/packages/odatav4/steps/08/README.md index d900dfe8b..e7b6bd285 100644 --- a/packages/odatav4/steps/08/README.md +++ b/packages/odatav4/steps/08/README.md @@ -121,14 +121,16 @@ We add the missing texts to the properties file. And now we are done! We built a simple application with user data from an OData V4 service. We can display, edit, create, and delete users. And we use OData V4 features such as batch groups and automatic type detection. -**Related Information** +*** -[Bindings](https://sdk.openui5.org/topic/54e0ddf695af4a6c978472cecb01c64d "Bindings connect OpenUI5 view elements to model data, allowing changes in the model to be reflected in the view element and vice versa.") +**Next:** [Step 9: List-Detail Scenario](../09/README.md) -[OData Operations](https://sdk.openui5.org/topic/b54f7895b7594c61a83fa7257fa9d13f "The OData V4 model supports OData operations (ActionImport, FunctionImport, bound Actions and bound Functions). Unbound parameters are limited to primitive values.") +**Previous:** [Step 7: Delete](../07/README.md) *** -**Next:** [Step 9: List-Detail Scenario](../09/README.md) +**Related Information** -**Previous:** [Step 7: Delete](../07/README.md) +[Bindings](https://sdk.openui5.org/topic/54e0ddf695af4a6c978472cecb01c64d "Bindings connect OpenUI5 view elements to model data, allowing changes in the model to be reflected in the view element and vice versa.") + +[OData Operations](https://sdk.openui5.org/topic/b54f7895b7594c61a83fa7257fa9d13f "The OData V4 model supports OData operations (ActionImport, FunctionImport, bound Actions and bound Functions). Unbound parameters are limited to primitive values.") diff --git a/packages/odatav4/steps/09/README.md b/packages/odatav4/steps/09/README.md index e4b1b35f0..664f096d3 100644 --- a/packages/odatav4/steps/09/README.md +++ b/packages/odatav4/steps/09/README.md @@ -345,10 +345,14 @@ nameLabelText=Name We add the missing texts to the properties file. -**Related Information** +*** -[Data Reuse](https://sdk.openui5.org/topic/648e360fa22d46248ca783dc6eb44531 "The OData V4 model keeps data with respect to bindings, which allows different views on the same data, but also means that data is not automatically shared between bindings. There are mechanisms for sharing data to avoid redundant requests and to keep the same data in different controls in sync.") +**Next:** [Step 10: Enable Data Reuse](../10/README.md) + +**Previous:** [Step 8: OData Operations](../08/README.md) *** -**Previous:** [Step 8: OData Operations](../08/README.md) +**Related Information** + +[Data Reuse](https://sdk.openui5.org/topic/648e360fa22d46248ca783dc6eb44531 "The OData V4 model keeps data with respect to bindings, which allows different views on the same data, but also means that data is not automatically shared between bindings. There are mechanisms for sharing data to avoid redundant requests and to keep the same data in different controls in sync.") diff --git a/packages/odatav4/steps/09/assets/Tut_OD4_Step_9_6e9025b.png b/packages/odatav4/steps/09/assets/Tut_OD4_Step_9_6e9025b.png new file mode 100644 index 000000000..3faa3104c Binary files /dev/null and b/packages/odatav4/steps/09/assets/Tut_OD4_Step_9_6e9025b.png differ diff --git a/packages/odatav4/steps/10/README.md b/packages/odatav4/steps/10/README.md index 3effe4432..c8db093d2 100644 --- a/packages/odatav4/steps/10/README.md +++ b/packages/odatav4/steps/10/README.md @@ -114,10 +114,14 @@ For the new context we set `keepAlive` to `true` and add `_setDetailArea` as an You can use the `Context#setKeepAlive` method to prevent the destruction of information shown in the detail area when the selected user is no longer part of the list from which the information was selected. This could otherwise happen if you filter or sort the list. -**Related Information** +*** -[Extending the Lifetime of a Context that is not Used Exclusively by a Table Collection](https://sdk.openui5.org/topic/648e360fa22d46248ca783dc6eb44531.html#loio648e360fa22d46248ca783dc6eb44531/section_ELC) +**Next:** [Step 11: Add Table with :n Navigation to Detail Area](../11/README.md) + +**Previous:** [Step 9: List-Detail Scenario](../09/README.md) *** -**Next:** [Step 11: Add Table with :n Navigation to Detail Area](../11/README.md) +**Related Information** + +[Extending the Lifetime of a Context that is not Used Exclusively by a Table Collection](https://sdk.openui5.org/topic/648e360fa22d46248ca783dc6eb44531.html#loio648e360fa22d46248ca783dc6eb44531/section_ELC) diff --git a/packages/odatav4/steps/10/assets/Tut_OD4_Step_9_6e9025b.png b/packages/odatav4/steps/10/assets/Tut_OD4_Step_9_6e9025b.png new file mode 100644 index 000000000..3faa3104c Binary files /dev/null and b/packages/odatav4/steps/10/assets/Tut_OD4_Step_9_6e9025b.png differ diff --git a/packages/odatav4/steps/11/README.md b/packages/odatav4/steps/11/README.md index fa4da0542..eaa81bea5 100644 --- a/packages/odatav4/steps/11/README.md +++ b/packages/odatav4/steps/11/README.md @@ -93,6 +93,4 @@ We extend the detail area of the `appView` by adding a table after the `FlexBox` *** -**Next:** [Step 1: The Initial App](../01/README.md) - **Previous:** [Step 10: Enable Data Reuse](../10/README.md) diff --git a/packages/odatav4/steps/11/assets/Tut_OD4_Step_11_45abd62.png b/packages/odatav4/steps/11/assets/Tut_OD4_Step_11_45abd62.png new file mode 100644 index 000000000..fa4f7d72b Binary files /dev/null and b/packages/odatav4/steps/11/assets/Tut_OD4_Step_11_45abd62.png differ diff --git a/packages/quickstart/README.md b/packages/quickstart/README.md index d79572f09..1edb9fe2b 100644 --- a/packages/quickstart/README.md +++ b/packages/quickstart/README.md @@ -8,9 +8,7 @@ We set up the development environment and bootstrap OpenUI5 in an HTML page with ### Preview -![The second page of the final quickstart app titled 'Create Enterprise-Ready Web Apps with Ease', showing the OpenUI5 phoenix logo, a 'This is UI5!' heading, six feature InfoLabels, and an expandable 'Are you ready?' panel.](steps/03/assets/loio79e1157d948c488c9717ef840fa9b396_LowRes.png) - -<sup>*The second page of the finished app, reached by pressing the Go! button*</sup> +![Three cascading browser windows show the three steps of the Quickstart tutorial: a "Ready..." button with a "Hello World!" message, a "Steady..." button with a "Hello App!" message, and a "Go!" button with a "Hello UI5!" message.](assets/loio443918d392b94ec58e0f84df6ec54d46_LowRes.png "The OpenUI5 application that is going to be built in this tutorial") ### Steps diff --git a/packages/quickstart/assets/loio443918d392b94ec58e0f84df6ec54d46_LowRes.png b/packages/quickstart/assets/loio443918d392b94ec58e0f84df6ec54d46_LowRes.png new file mode 100644 index 000000000..a80d23847 Binary files /dev/null and b/packages/quickstart/assets/loio443918d392b94ec58e0f84df6ec54d46_LowRes.png differ diff --git a/packages/quickstart/steps/01/README.md b/packages/quickstart/steps/01/README.md index 677bf497c..6bcfcb89a 100644 --- a/packages/quickstart/steps/01/README.md +++ b/packages/quickstart/steps/01/README.md @@ -11,8 +11,6 @@ Let's get you ready for your journey! We bootstrap OpenUI5 in an HTML page and i ![The browser shows a "Ready" button that triggers a "Hello World" message](assets/loio9c157e9764b846fea7de519d141c33ac_LowRes.png "The browser shows a Ready button that triggers a Hello World message") -<sup>*The browser shows a "Ready" button that triggers a "Hello World" message*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 1](https://ui5.github.io/tutorials/quickstart/build/01/index-cdn.html). *** diff --git a/packages/quickstart/steps/02/README.md b/packages/quickstart/steps/02/README.md index a33fd8fe8..b93610383 100644 --- a/packages/quickstart/steps/02/README.md +++ b/packages/quickstart/steps/02/README.md @@ -11,8 +11,6 @@ Now we extend our minimalist HTML page to a basic app with a view and a controll ![The browser shows a Steady button in an app](assets/loio240ef5357d7f4d36955092cdaf1884a2_LowRes.png "The browser shows a Steady button in an app") -<sup>*The browser shows a Steady button in an app*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 2](https://ui5.github.io/tutorials/quickstart/build/02/index-cdn.html). *** diff --git a/packages/quickstart/steps/03/README.md b/packages/quickstart/steps/03/README.md index b3afdb86e..dce0ae1fa 100644 --- a/packages/quickstart/steps/03/README.md +++ b/packages/quickstart/steps/03/README.md @@ -11,8 +11,6 @@ Finally, we add a second page to our app showcasing some of the key OpenUI5 conc ![The second page shows plenty of UI controls and concepts to explore](assets/loio79e1157d948c488c9717ef840fa9b396_LowRes.png "The second page shows plenty of UI controls and concepts to explore") -<sup>*The second page shows plenty of UI controls and concepts to explore*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 3](https://ui5.github.io/tutorials/quickstart/build/03/index-cdn.html). *** diff --git a/packages/walkthrough/steps/01/README.md b/packages/walkthrough/steps/01/README.md index 44e11eedd..488913d80 100644 --- a/packages/walkthrough/steps/01/README.md +++ b/packages/walkthrough/steps/01/README.md @@ -11,8 +11,6 @@ As you know, OpenUI5 is all about HTML5. Let's get started with building a first ![The browser shows the text "Hello World"](assets/loio1dd456361379431aab7e5bcdaaeff00f_LowRes.png "The browser shows the text "Hello World"") -<sup>*The browser shows the text "Hello World"*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 1](https://ui5.github.io/tutorials/walkthrough/build/01/index.html). *** diff --git a/packages/walkthrough/steps/02/README.md b/packages/walkthrough/steps/02/README.md index 2233fc49d..1e48441e6 100644 --- a/packages/walkthrough/steps/02/README.md +++ b/packages/walkthrough/steps/02/README.md @@ -11,8 +11,6 @@ Before we can do something with OpenUI5, we need to load and initialize it. This ![An alert "UI5 is ready" is displayed](assets/loio0f6b6b9dc46a474da9287c382c8d3456_LowRes.png "An alert "UI5 is ready" is displayed") -<sup>*An alert "UI5 is ready" is displayed*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 2](https://ui5.github.io/tutorials/walkthrough/build/02/index-cdn.html). *** diff --git a/packages/walkthrough/steps/03/README.md b/packages/walkthrough/steps/03/README.md index 20f39e3c9..e1b954062 100644 --- a/packages/walkthrough/steps/03/README.md +++ b/packages/walkthrough/steps/03/README.md @@ -8,9 +8,7 @@ Now it is time to build our first little UI by replacing the “Hello World” t ### Preview -![The "Hello World" text is now displayed by a OpenUI5 control](assets/loio30a42d381b9e4388bf7fdc0b941e5381_LowRes.png "The "Hello World" text is now displayed by a OpenUI5 control") - -<sup>*The "Hello World" text is now displayed by an OpenUI5 control*</sup> +![The "Hello World" text is now displayed by an OpenUI5 control](assets/loio30a42d381b9e4388bf7fdc0b941e5381_LowRes.png "The "Hello World" text is now displayed by an OpenUI5 control") You can access the live preview by clicking on this link: [🔗 Live Preview of Step 3](https://ui5.github.io/tutorials/walkthrough/build/03/index-cdn.html). diff --git a/packages/walkthrough/steps/04/README.md b/packages/walkthrough/steps/04/README.md index ba36e2b12..130a9d747 100644 --- a/packages/walkthrough/steps/04/README.md +++ b/packages/walkthrough/steps/04/README.md @@ -10,9 +10,7 @@ When working with OpenUI5, we recommend the use of XML views, as this produces t ### Preview -![The "Hello World" text is now displayed by a OpenUI5 control \(No visual changes to last step\)](assets/loio05f6775a39d3409ea673f4acc3812142_LowRes.png "The "Hello World" text is now displayed by a OpenUI5 control \(No visual changes to last step\)") - -<sup>*The "Hello World" text is now displayed by an OpenUI5 control \(No visual changes to last step\)*</sup> +![The "Hello World" text is now displayed by an OpenUI5 control \(No visual changes to last step\)](assets/loio05f6775a39d3409ea673f4acc3812142_LowRes.png "The "Hello World" text is now displayed by an OpenUI5 control \(No visual changes to last step\)") You can access the live preview by clicking on this link: [🔗 Live Preview of Step 4](https://ui5.github.io/tutorials/walkthrough/build/04/index-cdn.html). diff --git a/packages/walkthrough/steps/05/README.md b/packages/walkthrough/steps/05/README.md index c0eab1d8f..e3af1bc5d 100644 --- a/packages/walkthrough/steps/05/README.md +++ b/packages/walkthrough/steps/05/README.md @@ -11,8 +11,6 @@ In this step, we replace the text with a button and show the “Hello World” m ![A Say Hello button is added](assets/loiocedfdf89b30643ddbfcab1fe50bfa892_LowRes.png "A Say Hello button is added") -<sup>*A Say Hello button is added*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 5](https://ui5.github.io/tutorials/walkthrough/build/05/index-cdn.html). *** diff --git a/packages/walkthrough/steps/06/README.md b/packages/walkthrough/steps/06/README.md index 73db46fa2..82d63f4d1 100644 --- a/packages/walkthrough/steps/06/README.md +++ b/packages/walkthrough/steps/06/README.md @@ -10,8 +10,6 @@ In OpenUI5, resources are often referred to as modules. In this step, we replace ![A message toast displays the "Hello World" message](assets/loio2f629a95211f49afa367b60d233fb390_LowRes.png "A message toast displays the "Hello World" message") -<sup>*A message toast displays the "Hello World" message*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 6](https://ui5.github.io/tutorials/walkthrough/build/06/index-cdn.html). *** diff --git a/packages/walkthrough/steps/07/README.md b/packages/walkthrough/steps/07/README.md index bb3ad4aab..3381e11b8 100644 --- a/packages/walkthrough/steps/07/README.md +++ b/packages/walkthrough/steps/07/README.md @@ -12,8 +12,6 @@ We'll create a view model in our controller, add an input field to our app, bind ![An input field and a description displaying the value of the input field](assets/loioafc105517a644407bd90662e3d94ea01_LowRes.png "An input field and a description displaying the value of the input field") -<sup>*An input field and a description displaying the value of the input field*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 7](https://ui5.github.io/tutorials/walkthrough/build/07/index-cdn.html). *** diff --git a/packages/walkthrough/steps/08/README.md b/packages/walkthrough/steps/08/README.md index ac3ea118c..5e764f1bf 100644 --- a/packages/walkthrough/steps/08/README.md +++ b/packages/walkthrough/steps/08/README.md @@ -10,9 +10,7 @@ This way, they are all in a central place and can be easily translated into othe ### Preview -![An input field and a description displaying the value of the input field \(No visual changes to last step\(](assets/loio0eb579e2f2a64c5a9894086322c7faa0_LowRes.png "An input field and a description displaying the value of the input field \(No visual changes to last step\(") - -<sup>*An input field and a description displaying the value of the input field \(No visual changes to last step\)*</sup> +![An input field and a description displaying the value of the input field \(No visual changes to last step\)](assets/loio0eb579e2f2a64c5a9894086322c7faa0_LowRes.png "An input field and a description displaying the value of the input field \(No visual changes to last step\)") You can access the live preview by clicking on this link: [🔗 Live Preview of Step 8](https://ui5.github.io/tutorials/walkthrough/build/08/index-cdn.html). diff --git a/packages/walkthrough/steps/09/README.md b/packages/walkthrough/steps/09/README.md index e136dc52f..519c4df31 100644 --- a/packages/walkthrough/steps/09/README.md +++ b/packages/walkthrough/steps/09/README.md @@ -15,8 +15,6 @@ By encapsulating our application as a component, we can seamlessly integrate it ![An input field and a description displaying the value of the input field (No visual changes to last step)](assets/loiocac9bcfa902c44c496d115acd7ee7376_LowRes.png "An input field and a description displaying the value of the input field (No visual changes to last step)") -<sup>*An input field and a description displaying the value of the input field \(No visual changes to last step\)*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 9](https://ui5.github.io/tutorials/walkthrough/build/09/index-cdn.html). After this step your project structure will look like the figure below. We will create the `Component.ts/.js` file now and modify the related files in the app. diff --git a/packages/walkthrough/steps/10/README.md b/packages/walkthrough/steps/10/README.md index 908cbc8b3..c2e3d626f 100644 --- a/packages/walkthrough/steps/10/README.md +++ b/packages/walkthrough/steps/10/README.md @@ -11,8 +11,6 @@ Instead of relying on a local HTML file for the bootstrap, the manifest is parse ### Preview ![An input field and a description displaying the value of the input field \(No visual changes to last step\)](assets/loio7b2aef85c016485da4a31c087bf4c0f0_LowRes.png "An input field and a description displaying the value of the input field \(No visual changes to last step\)") -<sup>*An input field and a description displaying the value of the input field \(No visual changes to last step\)*</sub> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 10](https://ui5.github.io/tutorials/walkthrough/build/10/index-cdn.html). *** diff --git a/packages/walkthrough/steps/11/README.md b/packages/walkthrough/steps/11/README.md index 2fe84f48d..26c739c48 100644 --- a/packages/walkthrough/steps/11/README.md +++ b/packages/walkthrough/steps/11/README.md @@ -10,8 +10,6 @@ After all the work on the app structure it’s time to improve the look of our a ![A panel is now displaying the controls from the previous steps](assets/loio97feb5417c89462ead5b4259f3ecfd47_LowRes.png "A panel is now displaying the controls from the previous steps") -<sup>*A panel is now displaying the controls from the previous steps*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 11](https://ui5.github.io/tutorials/walkthrough/build/11/index-cdn.html). diff --git a/packages/walkthrough/steps/12/README.md b/packages/walkthrough/steps/12/README.md index a0033dc83..560c7a57a 100644 --- a/packages/walkthrough/steps/12/README.md +++ b/packages/walkthrough/steps/12/README.md @@ -8,9 +8,7 @@ Now we use a shell control as container for our app and use it as our new root e ### Preview -![The app is now run in a shall that limits the app width](assets/loio0becf3ee81f5486a864e3b39ba036402_LowRes.png "The app is now run in a shall that limits the app width") - -<sup>*The app is now run in a shell that limits the app width*</sup> +![The app is now run in a shell that limits the app width](assets/loio0becf3ee81f5486a864e3b39ba036402_LowRes.png "The app is now run in a shell that limits the app width") You can access the live preview by clicking on this link: [🔗 Live Preview of Step 12](https://ui5.github.io/tutorials/walkthrough/build/12/index-cdn.html). diff --git a/packages/walkthrough/steps/13/README.md b/packages/walkthrough/steps/13/README.md index 081457cd8..d415f397a 100644 --- a/packages/walkthrough/steps/13/README.md +++ b/packages/walkthrough/steps/13/README.md @@ -12,8 +12,6 @@ Instead of manually adding CSS to the controls, we will use the standard classes ![The layout of the panel and its content now has margins and padding](assets/loio0becf3ee81f5486a864e3b39ba036402_LowRes.png "The layout of the panel and its content now has margins and padding") -<sup>*The layout of the panel and its content now has margins and padding*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 13](https://ui5.github.io/tutorials/walkthrough/build/13/index-cdn.html). *** diff --git a/packages/walkthrough/steps/14/README.md b/packages/walkthrough/steps/14/README.md index 92db34b19..598b2c165 100644 --- a/packages/walkthrough/steps/14/README.md +++ b/packages/walkthrough/steps/14/README.md @@ -13,8 +13,6 @@ Sometimes we need to define some more fine-granular layouts and this is when we ![The space between the button and the input field is now smaller and the output text is bold](assets/loiod9a40e539b7c49c485be821efbd3821f_LowRes.png "The space between the button and the input field is now smaller and the output text is bold") -<sup>*The space between the button and the input field is now smaller and the output text is bold*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 14](https://ui5.github.io/tutorials/walkthrough/build/14/index-cdn.html). diff --git a/packages/walkthrough/steps/15/README.md b/packages/walkthrough/steps/15/README.md index 18ece209c..eb05bf7a3 100644 --- a/packages/walkthrough/steps/15/README.md +++ b/packages/walkthrough/steps/15/README.md @@ -10,8 +10,6 @@ Our panel content is getting more and more complex and now it is time to move th ![The panel content is now refactored to a separate view \(No visual changes to last step\)](assets/loiof3724d2f97e94a78b27d8ab01ff9c37d_LowRes.png "The panel content is now refactored to a separate view \(No visual changes to last step\)") -<sup>*The panel content is now refactored to a separate view \(No visual changes to last step\)*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 15](https://ui5.github.io/tutorials/walkthrough/build/15/index-cdn.html). *** @@ -164,7 +162,7 @@ We have now moved everything out of the app view and controller. The app control **Next:** [Step 16: Dialogs and Fragments](../16/README.md) -**Previous:** [Step 14: Margins and Paddings](../14/README.md) +**Previous:** [Step 14: Custom CSS and Theme Colors](../14/README.md) *** diff --git a/packages/walkthrough/steps/16/README.md b/packages/walkthrough/steps/16/README.md index 2c9919de2..d82578892 100644 --- a/packages/walkthrough/steps/16/README.md +++ b/packages/walkthrough/steps/16/README.md @@ -16,8 +16,6 @@ We will now add a dialog to our app. Dialogs are special, because they open on t ![A dialog opens when the new "Say Hello With Dialog" button is clicked](assets/loio0916080895e144ed8b31963bfb18e17f_LowRes.png "A dialog opens when the new "Say Hello With Dialog" button is clicked") -<sup>*A dialog opens when the new “Say Hello With Dialog” button is clicked*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 16](https://ui5.github.io/tutorials/walkthrough/build/16/index-cdn.html). *** @@ -164,7 +162,7 @@ It is a good practice to set a unique ID like `helloWorldButton` to key controls **Next:** [Step 17: Fragment Callbacks](../17/README.md) -**Previous:** [Step15: Nested Views](../15/README.md) +**Previous:** [Step 15: Nested Views](../15/README.md) *** diff --git a/packages/walkthrough/steps/17/README.md b/packages/walkthrough/steps/17/README.md index a9a2077c3..83651f02d 100644 --- a/packages/walkthrough/steps/17/README.md +++ b/packages/walkthrough/steps/17/README.md @@ -8,9 +8,7 @@ Now that we have integrated the dialog, it's time to add some user interaction. ### Preview -![The dialog now has an "OK" button](assets/loioc351bbd078824c43bf1758b0c3679cbd_LowRes.png "The dialog now has an "OK" button") - -<sup>*The dialog now has an "OK" button to close the dialog*</sup> +![The dialog now has an "OK" button to close the dialog](assets/loioc351bbd078824c43bf1758b0c3679cbd_LowRes.png "The dialog now has an "OK" button to close the dialog") You can access the live preview by clicking on this link: [🔗 Live Preview of Step 17](https://ui5.github.io/tutorials/walkthrough/build/17/index-cdn.html). *** diff --git a/packages/walkthrough/steps/18/README.md b/packages/walkthrough/steps/18/README.md index 2b8c66842..6da954ab8 100644 --- a/packages/walkthrough/steps/18/README.md +++ b/packages/walkthrough/steps/18/README.md @@ -10,8 +10,6 @@ Our dialog is still pretty much empty. Since OpenUI5 is shipped with a large ico ![An icon is now displayed in the dialog box](assets/loiofbc48e23cc7d45e393cc95bbbfc6e0a3_LowRes.png "An icon is now displayed in the dialog box") -<sup>*An icon is now displayed in the dialog box*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 18](https://ui5.github.io/tutorials/walkthrough/build/18/index-cdn.html). *** @@ -97,9 +95,9 @@ In the dialog fragment, we add an icon control to the content aggregation of the *** -**Next:** Step 19: [Aggregation Binding](../19/README.md) +**Next:** [Step 19: Aggregation Binding](../19/README.md) -**Previous** Step 17: [Fragment Callbacks](../17/README.md) +**Previous:** [Step 17: Fragment Callbacks](../17/README.md) *** diff --git a/packages/walkthrough/steps/19/README.md b/packages/walkthrough/steps/19/README.md index 080104687..e93cb2048 100644 --- a/packages/walkthrough/steps/19/README.md +++ b/packages/walkthrough/steps/19/README.md @@ -9,8 +9,6 @@ Now that we have established a good structure for our app, it's time to add some ![A list of invoices is displayed below the panel](assets/loiob05bdb47393b4abda3e1b54498959c38_LowRes.png "A list of invoices is displayed below the panel") -<sup>*A list of invoices is displayed below the panel*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 19](https://ui5.github.io/tutorials/walkthrough/build/19/index-cdn.html). *** @@ -192,9 +190,9 @@ In the app view we add a second view and assign it to our newly created InvoiceL *** -**Next:** Step 20: [Data Types](../20/README.md) +**Next:** [Step 20: Data Types](../20/README.md) -**Previous:** Step 18: [Icons](../18/README.md) +**Previous:** [Step 18: Icons](../18/README.md) *** diff --git a/packages/walkthrough/steps/20/README.md b/packages/walkthrough/steps/20/README.md index 911265d8f..79a9eb47f 100644 --- a/packages/walkthrough/steps/20/README.md +++ b/packages/walkthrough/steps/20/README.md @@ -10,8 +10,6 @@ The list of invoices is already looking nice, but what is an invoice without a p ![The list of invoices with prices and number units](assets/loiodc9e919119564ddab78b8d0550ecfa9b_LowRes.png "The list of invoices with prices and number units") -<sup>*The list of invoices with prices and number units*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 20](https://ui5.github.io/tutorials/walkthrough/build/20/index-cdn.html). *** diff --git a/packages/walkthrough/steps/21/README.md b/packages/walkthrough/steps/21/README.md index 9f5b6ee16..7fdb65f42 100644 --- a/packages/walkthrough/steps/21/README.md +++ b/packages/walkthrough/steps/21/README.md @@ -10,8 +10,6 @@ Sometimes the predefined types of OpenUI5 are not flexible enough and you want t ![The price is now formatted according to its number](assets/loio636b7008113442c8a4765bb710dd8ea9_LowRes.png "The price is now formatted according to its number") -<sup>*The price is now formatted according to its number*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 21](https://ui5.github.io/tutorials/walkthrough/build/21/index-cdn.html). *** diff --git a/packages/walkthrough/steps/22/README.md b/packages/walkthrough/steps/22/README.md index 55ef3bf84..e02cb58c6 100644 --- a/packages/walkthrough/steps/22/README.md +++ b/packages/walkthrough/steps/22/README.md @@ -10,8 +10,6 @@ If we want to do a more complex logic for formatting properties of our data mode ![A status is now displayed with a custom formatter](assets/loio7aa185a90dd7495cb6ec30c96bc80a54_LowRes.png "A status is now displayed with a custom formatter") -<sup>*A status is now displayed with a custom formatter*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 22](https://ui5.github.io/tutorials/walkthrough/build/22/index-cdn.html). *** diff --git a/packages/walkthrough/steps/23/README.md b/packages/walkthrough/steps/23/README.md index 2f8bbd115..502db02d6 100644 --- a/packages/walkthrough/steps/23/README.md +++ b/packages/walkthrough/steps/23/README.md @@ -10,8 +10,6 @@ In this step, we add a search field for our product list and define a filter tha ![A search field is displayed above the list](assets/loio472ab6bf88674c23ba103efd97163133_LowRes.png "A search field is displayed above the list") -<sup>*A search field is displayed above the list*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 23](https://ui5.github.io/tutorials/walkthrough/build/23/index-cdn.html). *** @@ -152,9 +150,9 @@ The search field is part of the list header and therefore, each change on the li *** -**Next:**[Step 24: Sorting and Grouping](../24/README.md) +**Next:** [Step 24: Sorting and Grouping](../24/README.md) -**Previous:**[Step 22: Custom Formatters](../22/README.md) +**Previous:** [Step 22: Custom Formatters](../22/README.md) *** diff --git a/packages/walkthrough/steps/24/README.md b/packages/walkthrough/steps/24/README.md index c18465290..9699e5379 100644 --- a/packages/walkthrough/steps/24/README.md +++ b/packages/walkthrough/steps/24/README.md @@ -11,8 +11,6 @@ To make our list of invoices even more user-friendly, we sort it alphabetically ![The list is now sorted and grouped by the shipping company](assets/loio33f71b44bb644d1fa2a0ab14f1fcc02a_LowRes.png "The list is now sorted and grouped by the shipping company") -<sup>*The list is now sorted and grouped by the shipping company*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 24](https://ui5.github.io/tutorials/walkthrough/build/24/index-cdn.html). *** @@ -84,7 +82,7 @@ We could define a custom group header factory if we wanted by setting the `group **Next:** [Step 25: Remote OData Service](../25/README.md) -**Previous:** [Step 23: Remote OData Service](../23/README.md) +**Previous:** [Step 23: Filtering](../23/README.md) *** diff --git a/packages/walkthrough/steps/25/README.md b/packages/walkthrough/steps/25/README.md index 439f4dd18..eb12280d6 100644 --- a/packages/walkthrough/steps/25/README.md +++ b/packages/walkthrough/steps/25/README.md @@ -14,8 +14,6 @@ In the real world, data often resides on remote servers and is accessed via an O ![Products from the OData invoices test service are now shown within our app](assets/loio5b76bb4b15eb44e1862d0b6c1c802571_LowRes.png "Products from the OData invoices test service are now shown within our app") -<sup>*Products from the OData invoices test service are now shown within our app*</sup> - *A real-time preview utilizing data from the OData remote service is currently unavailable in this setup. However, we assure you that it will work on your local machine as long as you avoid making any mistakes. So, give it a try and see the results for yourself!* *** diff --git a/packages/walkthrough/steps/26/README.md b/packages/walkthrough/steps/26/README.md index 44b04de1c..bc3008ca4 100644 --- a/packages/walkthrough/steps/26/README.md +++ b/packages/walkthrough/steps/26/README.md @@ -13,8 +13,6 @@ This system is the so-called back-end system that we will now simulate with anOp ![The list of invoices is now served by the Mock Server](assets/loiofe1403346ce9499f8bb102beaa4986d5_LowRes.png "The list of invoices is now served by the Mock Server") -<sup>*The list of invoices is now served by the Mock Server*</sup> - The folder structure of our app project is clearly separating test and productive files after this step. The new `test` folder now contains a new HTML page `mockServer.html` which will launch our application in test mode without calling the real service. The new `localService` folder contains a `metadata.xml` service description file for OData, the `mockserver.js` file that simulates a real service with local data, and the `mockdata` subfolder that contains the local test data \(`Invoices.json`\). diff --git a/packages/walkthrough/steps/27/README.md b/packages/walkthrough/steps/27/README.md index 829f12cf0..02d434ba9 100644 --- a/packages/walkthrough/steps/27/README.md +++ b/packages/walkthrough/steps/27/README.md @@ -15,8 +15,6 @@ Actually, every feature that we added to the app so far, would require a separat ![A unit test for our formatters is now available](assets/loio0d29491d96574cfe8d8158d60a0a32e2_LowRes.png "A unit test for our formatters is now available") -<sup>*A unit test for our formatters is now available*</sup> - We add a new folder `unit` under the `test` folder and a `model` subfolder where we will place our formatter unit test. The folder structure matches the app structure to easily find the corresponding unit tests. ```text diff --git a/packages/walkthrough/steps/28/README.md b/packages/walkthrough/steps/28/README.md index 29666ec9f..8ef21ca55 100644 --- a/packages/walkthrough/steps/28/README.md +++ b/packages/walkthrough/steps/28/README.md @@ -15,8 +15,6 @@ We haven’t thought about testing our interaction with the app yet, so in this ![An OPA test opens the "Hello" dialog from step 16](assets/loio250d5b92921d44a4b432cc0fade88cc9_LowRes.png "An OPA test opens the "Hello" dialog from step 16") -<sup>*An OPA test opens the "Hello" dialog from step 16*</sup> - We add a new folder `integration` below the `test` folder, where we put our new test cases. Page objects that help structuring such integration tests are put in the `pages` subfolder that we also create now. ```text diff --git a/packages/walkthrough/steps/29/README.md b/packages/walkthrough/steps/29/README.md index 33d0817d1..3b7c79d77 100644 --- a/packages/walkthrough/steps/29/README.md +++ b/packages/walkthrough/steps/29/README.md @@ -12,8 +12,6 @@ Luckily, OpenUI5 provides a couple of debugging tools that we can use within the ![The diagnostics window](assets/loio930de31b311f43ffa9df9261ca760da0_LowRes.png "The diagnostics window") - -<sup>*The diagnostics window*</sup> *The code in this step remains unchanged, except for the addition and subsequent removal of a bug using troubleshooting tools. As a result, there is no live preview or download available for this step.* @@ -104,8 +102,6 @@ Sometimes errors are not as easy to spot and you actually need to debug the Java ![Technical information dialog ](assets/loio34c4b02c74eb4848b8b720d86042bfdc_LowRes.png "Technical information dialog ") -<sup>*Technical information dialog*</sup> - For a more detailed explanation of the OpenUI5 support tools, go through the [Troubleshooting Tutorial](https://sdk.openui5.org/topic/5661952e72df471b932eddc10350c081.html) tutorial. If you're stuck and need help for some development task, you can also post a question in the OpenUI5-related forums, for example in the [SAP Community](https://www.sap.com/community/topic/ui5.html) or on [Stack Overflow](https://stackoverflow.com/search?q=openui5). diff --git a/packages/walkthrough/steps/30/README.md b/packages/walkthrough/steps/30/README.md index b097a2810..a3002936e 100644 --- a/packages/walkthrough/steps/30/README.md +++ b/packages/walkthrough/steps/30/README.md @@ -13,8 +13,6 @@ In this step, we will use the OpenUI5 navigation features to load and show a sep ![A second page is added to display the invoice](assets/loio94152a595fe24d45b12223e0abcccb9c_LowRes.png "A second page is added to display the invoice") -<sup>*A second page is added to display the invoice*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 30](https://ui5.github.io/tutorials/walkthrough/build/30/test/mockServer-cdn.html). *** @@ -353,7 +351,7 @@ If you now open the app, you should now see the detail page when clicking an ite *** -**Next:** [Step 31: Routing and Navigation](../31/README.md) +**Next:** [Step 31: Routing with Parameters](../31/README.md) **Previous:** [Step 29: Debugging Tools](../29/README.md) diff --git a/packages/walkthrough/steps/31/README.md b/packages/walkthrough/steps/31/README.md index d87d1fabc..139c0cc56 100644 --- a/packages/walkthrough/steps/31/README.md +++ b/packages/walkthrough/steps/31/README.md @@ -13,8 +13,6 @@ To make this work, we have to pass over the information which item has been sele ![The selected invoice details are now shown in the details page](assets/loio31da9d48ae204c36a991146b90648c21_LowRes.png "The selected invoice details are now shown in the details page") -<sup>*The selected invoice details are now shown in the details page*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 31](https://ui5.github.io/tutorials/walkthrough/build/31/test/mockServer-cdn.html). *** diff --git a/packages/walkthrough/steps/32/README.md b/packages/walkthrough/steps/32/README.md index f293be2aa..667d8cb1c 100644 --- a/packages/walkthrough/steps/32/README.md +++ b/packages/walkthrough/steps/32/README.md @@ -11,8 +11,6 @@ Now we can navigate to our detail page and display an invoice, but we cannot go ![A back button is now displayed on the detail page](assets/loio33a8341077bb458685274c64d2317f6b_LowRes.png "A back button is now displayed on the detail page") -<sup>*A back button is now displayed on the detail page*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 32](https://ui5.github.io/tutorials/walkthrough/build/32/test/mockServer-cdn.html). *** @@ -120,7 +118,7 @@ You should now see a back button when navigating to the detail page and being ab **Next:** [Step 33: Custom Controls](../33/README.md) -**Previous:** [Step 31: Routing and Navigation](../31/README.md) +**Previous:** [Step 31: Routing with Parameters](../31/README.md) *** diff --git a/packages/walkthrough/steps/33/README.md b/packages/walkthrough/steps/33/README.md index 72fe6a679..df6b64b2c 100644 --- a/packages/walkthrough/steps/33/README.md +++ b/packages/walkthrough/steps/33/README.md @@ -10,8 +10,6 @@ In this step, we are going to extend the functionality of OpenUI5 with a custom ![A custom product rating control is added to the detail page](assets/loio21dd14c37b67473b817c8865f168f668_LowRes.png "A custom product rating control is added to the detail page") -<sup>*A custom product rating control is added to the detail page*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 33](https://ui5.github.io/tutorials/walkthrough/build/33/test/mockServer-cdn.html). *** diff --git a/packages/walkthrough/steps/34/README.md b/packages/walkthrough/steps/34/README.md index d6808beba..6baaeddae 100644 --- a/packages/walkthrough/steps/34/README.md +++ b/packages/walkthrough/steps/34/README.md @@ -11,8 +11,6 @@ In this step, we improve the responsiveness of our app. OpenUI5 applications can ![A responsive table is hiding some of the columns on small devices](assets/loiocc3f2e0d8ac6471288af6495836c2f07_LowRes.png "A responsive table is hiding some of the columns on small devices") -<sup>*A responsive table is hiding some of the columns on small devices*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 34](https://ui5.github.io/tutorials/walkthrough/build/34/test/mockServer-cdn.html). @@ -197,9 +195,9 @@ We can see the results when we decrease the browser's screen size or open the ap *** -**Next:** [Step 35: Routing and Navigation](../35/README.md) +**Next:** [Step 35: Device Adaptation](../35/README.md) -**Previous:** [Step 33: Debugging Tools](../33/README.md) +**Previous:** [Step 33: Custom Controls](../33/README.md) *** diff --git a/packages/walkthrough/steps/35/README.md b/packages/walkthrough/steps/35/README.md index fe049aee1..205edcad4 100644 --- a/packages/walkthrough/steps/35/README.md +++ b/packages/walkthrough/steps/35/README.md @@ -10,8 +10,6 @@ We now configure the visibility and properties of controls based on the device t ![On phone devices, the panel is collapsed to save screen space and a button is hidden](assets/loio0b0d57e04e574d7fbc4b10395e6cb260_LowRes.png "On phone devices, the panel is collapsed to save screen space and a button is hidden") -<sup>*On phone devices, the panel is collapsed to save screen space and a button is hidden*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 35](https://ui5.github.io/tutorials/walkthrough/build/35/test/mockServer-cdn.html). *** @@ -299,7 +297,7 @@ Optimize your application for the different screen sizes of phone, tablet, and d **Next:** [Step 36: Content Density](../36/README.md) -**Previous:** [Step 34: Routing and Navigation](../34/README.md) +**Previous:** [Step 34: Responsiveness](../34/README.md) *** diff --git a/packages/walkthrough/steps/36/README.md b/packages/walkthrough/steps/36/README.md index c1250ffc7..9af6c4979 100644 --- a/packages/walkthrough/steps/36/README.md +++ b/packages/walkthrough/steps/36/README.md @@ -8,9 +8,7 @@ In this step of our Walkthrough tutorial, we adjust the content density based on ### Preview -![The content density is compact on desktop devices and cozy on touch-enabled devices](assets/loiof216b131c492448d8a1df25db2b9a26d_LowRes.png "The content density is compact on desktop devices and cozy on touch-enabled devices") - -<sup>*The content density is compact on devices without a touch screen and cozy on touch-enabled devices*</sup> +![The content density is compact on devices without a touch screen and cozy on touch-enabled devices](assets/loiof216b131c492448d8a1df25db2b9a26d_LowRes.png "The content density is compact on devices without a touch screen and cozy on touch-enabled devices") You can access the live preview by clicking on this link: [🔗 Live Preview of Step 36](https://ui5.github.io/tutorials/walkthrough/build/36/test/mockServer-cdn.html). @@ -144,7 +142,7 @@ As we have just enabled the app to run in both modes depending on the devices ca **Next:** [Step 37: Accessibility](../37/README.md) -**Previous:** [Step 35: Routing and Navigation](../35/README.md) +**Previous:** [Step 35: Device Adaptation](../35/README.md) *** diff --git a/packages/walkthrough/steps/37/README.md b/packages/walkthrough/steps/37/README.md index b809be780..2e275baf9 100644 --- a/packages/walkthrough/steps/37/README.md +++ b/packages/walkthrough/steps/37/README.md @@ -17,8 +17,6 @@ One part of the ARIA attribute set are the so-called landmarks. You can compare ![Landmarks in our app](assets/loiob35deda1ebe1433fbf0ff066f6e3fc4b_LowRes.png "Landmarks in our app") -<sup>*Landmarks in our app*</sup> - You can access the live preview by clicking on this link: [🔗 Live Preview of Step 37](https://ui5.github.io/tutorials/walkthrough/build/37/test/mockServer-cdn.html). *** diff --git a/packages/walkthrough/steps/38/README.md b/packages/walkthrough/steps/38/README.md index 7161629cd..95d001ce7 100644 --- a/packages/walkthrough/steps/38/README.md +++ b/packages/walkthrough/steps/38/README.md @@ -9,9 +9,7 @@ In this step we're going to build our application and consume the speed of a bui ### Preview -![The UI5 application is built and served](assets/loiofb12cea5ac9b45bb9007aac5a1a8689f_LowRes.png "The UI5 application is built and served") - -<sup>*The OpenUI5 application is built and served*</sup> +![The OpenUI5 application is built and served](assets/loiofb12cea5ac9b45bb9007aac5a1a8689f_LowRes.png "The OpenUI5 application is built and served") *** diff --git a/tools/builder/prepare-gh-pages.js b/tools/builder/prepare-gh-pages.js index 4ae2d7fbe..230ef707b 100644 --- a/tools/builder/prepare-gh-pages.js +++ b/tools/builder/prepare-gh-pages.js @@ -70,6 +70,42 @@ function removeTSfromUI5YAML(ui5yaml) { mkdirSync(join(cwd, "dist"), { recursive: true }); console.log(`👉 Copying root README.md...`); + // Display titles for the per-tutorial breadcrumb, read from each tutorial's + // overview README (first `# ` heading). Populated once the tutorials are + // known (see below); step pages get a breadcrumb injected in rewriteLinks. + const tutorialTitles = {}; + + function extractTitle(readmePath) { + const md = readFileSync(readmePath, { encoding: "utf8" }); + const match = md.match(/^#\s+(.+)$/m); + return match ? match[1].trim() : null; + } + + // Build a "UI5 Tutorials › <Tutorial>" breadcrumb for step pages only. The + // tutorial name links to that tutorial's overview so a reader can get back + // to it (not just to the repo root). Links are relative to the page's depth + // so they work under the site's `/tutorials/` base path. Returns "" for the + // root index and tutorial-overview pages. + function breadcrumbFor(permalink) { + const match = permalink.match(/^([^/]+)\/build\/[^/]+\/README\.html$/); + if (!match) { + return ""; + } + const slug = match[1]; + const title = tutorialTitles[slug] || slug; + const depth = permalink.split("/").length - 1; // directories above the file + const up = (n) => "../".repeat(n); + const rootHref = `${up(depth)}index.html`; + const overviewHref = `${up(depth - 1)}index.html`; + return ( + `<nav class="tutorial-breadcrumb">` + + `<a href="${rootHref}">UI5 Tutorials</a>` + + ` <span class="tutorial-breadcrumb-sep">›</span> ` + + `<a href="${overviewHref}">${title}</a>` + + `</nav>\n\n` + ); + } + function escapeCodeBlocks(content) { // Jekyll/Liquid (used by GitHub Pages) interprets `{{ ... }}` and `{% ... %}` // inside the markdown. Code snippets (fenced blocks and inline code) often @@ -91,7 +127,8 @@ function removeTSfromUI5YAML(ui5yaml) { function rewriteLinks(file) { let permalink = file.split("dist/")[1].replace(".md", ".html"); const title = "OpenUI5 Tutorials"; - let content = `---\ntitle: ${title}\npermalink: ${permalink}\n---\n\n${readFileSync(file, { encoding: "utf8"})}`; + const breadcrumb = breadcrumbFor(permalink); + let content = `---\ntitle: ${title}\npermalink: ${permalink}\n---\n\n${breadcrumb}${readFileSync(file, { encoding: "utf8"})}`; content = content.replace(/README\.md/g, "index.html"); content = content.replace(/\.\/packages\//g, "./"); content = content.replace(/\[LICENSE\]\([\.\/\w]*\)/g, "[LICENSE](https://github.com/UI5/tutorials/blob/-/LICENSE)"); @@ -120,6 +157,15 @@ function removeTSfromUI5YAML(ui5yaml) { const tutorials = getTutorials(); console.log(`👉 Found tutorials: ${tutorials.join(", ")}`); + // Read each tutorial's display title up front so step-page breadcrumbs + // (injected by rewriteLinks) can name the tutorial they belong to. + for (const tutorial of tutorials) { + const overview = join(cwd, "packages", tutorial, "README.md"); + if (existsSync(overview)) { + tutorialTitles[tutorial] = extractTitle(overview) || tutorial; + } + } + for (const tutorial of tutorials) { const tutorialDir = join(cwd, "packages", tutorial); const distTutorialDir = join(cwd, "dist", tutorial); diff --git a/tools/dev-server/server.js b/tools/dev-server/server.js index 56d73b056..40c45f578 100644 --- a/tools/dev-server/server.js +++ b/tools/dev-server/server.js @@ -57,6 +57,34 @@ function rewriteGhPagesUrls(html) { return html; } +/** + * Build the "UI5 Tutorials › <Tutorial>" breadcrumb for a step page, mirroring + * the production breadcrumb injected by tools/builder/prepare-gh-pages.js. On + * the dev server, step READMEs are served at /packages/<slug>/steps/NN/README.md + * and the tutorial overview at /packages/<slug>/. Returns "" for any page that + * is not a step page (root, overview). + */ +function breadcrumbFor(reqPath) { + const match = reqPath.match(/^\/packages\/([^/]+)\/steps\/[^/]+\/README\.md$/); + if (!match) { + return ""; + } + const slug = match[1]; + const overview = join(cwd, "packages", slug, "README.md"); + let title = slug; + if (existsSync(overview)) { + const md = readFileSync(overview, { encoding: "utf-8" }); + title = md.match(/^#\s+(.+)$/m)?.[1]?.trim() || slug; + } + return ( + `<nav class="tutorial-breadcrumb">` + + `<a href="/">UI5 Tutorials</a>` + + ` <span class="tutorial-breadcrumb-sep">›</span> ` + + `<a href="/packages/${slug}/">${title}</a>` + + `</nav>\n` + ); +} + async function getTemplate() { const headContent = readFileSync(join(cwd, "_includes/head-custom.html"), { encoding: "utf-8" }); let template = readFileSync(join(__dirname, "ghpage-template.hbs"), { encoding: "utf-8" }); @@ -116,6 +144,7 @@ app.use(async (req, res, next) => { const md = readFileSync(file, { encoding: "utf-8" }); let bodyContent = await convertMarkdown(md); bodyContent = rewriteGhPagesUrls(bodyContent); + bodyContent = breadcrumbFor(reqUrlWithoutParams) + bodyContent; const templateFn = await getTemplate(); // get title as first line in the md file which starts with hashes, which indicates it is a title of some kind const title = md.match(/^##* (.+)$/m)?.[1] || reqUrlWithoutParams;