diff --git a/CODEOWNERS b/CODEOWNERS index a630dbace1..f9d33fc4a7 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -5,7 +5,7 @@ # For more details, read the following article on GitHub: https://help.github.com/articles/about-codeowners/. # These are the default owners for the whole content of the `luigi` repository. The default owners are automatically added as reviewers when you open a pull request, unless different owners are specified in the file. -* @pekura @maxmarkus @jesusreal @hardl @antiheld @johannesdoberer @marynaKhromova @zarkosimic +* @pekura @maxmarkus @hardl @antiheld @johannesdoberer @marynaKhromova @zarkosimic # Owners of all .md files in the repository *.md @bszwarc @kazydek @klaudiagrz @tomekpapiernik @mmitoraj @alexandra-simeonova @majakurcius diff --git a/README.md b/README.md index 017976160e..26c4ea1528 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,8 @@ import { // } from '@kyma-project/luigi-client'; ``` ->**NOTE**: The angular example application is not fully compatible with IE11. + +> **NOTE**: The angular example application is not fully compatible with IE11. ## Development @@ -71,8 +72,8 @@ For security reasons, follow these guidelines when developing a micro frontend: - Maintain a whitelist with trusted domains and compare it with the origin of the Luigi Core application. The origin will be passed when you call the init listener in your micro frontend. Stop further processing if the origin does not match. ->**NOTE**: Luigi follows these [sandbox rules for iframes](https://github.com/SAP/luigi/blob/af1deebb392dcec6490f72576e32eb5853a894bc/core/src/utilities/helpers/iframe-helpers.js#L140). - + +> **NOTE**: Luigi follows these [sandbox rules for iframes](https://github.com/SAP/luigi/blob/af1deebb392dcec6490f72576e32eb5853a894bc/core/src/utilities/helpers/iframe-helpers.js#L140). ### Code formatting for contributors diff --git a/client/public/luigi-client.d.ts b/client/public/luigi-client.d.ts index 3a1259ec09..21e9a59b0c 100644 --- a/client/public/luigi-client.d.ts +++ b/client/public/luigi-client.d.ts @@ -395,7 +395,8 @@ export type getContext = () => Context; /** * Returns the node parameters of the active URL. * Node parameters are defined like URL query parameters but with a specific prefix allowing Luigi to pass them to the micro frontend view. The default prefix is **~** and you can use it in the following way: `https://my.luigi.app/home/products?~sort=asc~page=3`. - * >**NOTE:** some special characters (`<`, `>`, `"`, `'`, `/`) in node parameters are HTML-encoded. + * + * > **NOTE:** some special characters (`<`, `>`, `"`, `'`, `/`) in node parameters are HTML-encoded. * @returns {Object} node parameters, where the object property name is the node parameter name without the prefix, and its value is the value of the node parameter. For example `{sort: 'asc', page: 3}`. * @memberof Lifecycle */ @@ -406,7 +407,8 @@ export type getNodeParams = () => NodeParams; * Returns the dynamic path parameters of the active URL. * Path parameters are defined by navigation nodes with a dynamic **pathSegment** value starting with **:**, such as **productId**. * All path parameters in the current navigation path (as defined by the active URL) are returned. - * >**NOTE:** some special characters (`<`, `>`, `"`, `'`, `/`) in path parameters are HTML-encoded. + * + * > **NOTE:** some special characters (`<`, `>`, `"`, `'`, `/`) in path parameters are HTML-encoded. * @returns {Object} path parameters, where the object property name is the path parameter name without the prefix, and its value is the actual value of the path parameter. For example ` {productId: 1234, ...}`. * @memberof Lifecycle */ diff --git a/client/src/lifecycleManager.js b/client/src/lifecycleManager.js index c9324210a2..d17cd73661 100644 --- a/client/src/lifecycleManager.js +++ b/client/src/lifecycleManager.js @@ -341,7 +341,8 @@ class LifecycleManager extends LuigiClientBase { /** * Returns the node parameters of the active URL. * Node parameters are defined like URL query parameters but with a specific prefix allowing Luigi to pass them to the micro frontend view. The default prefix is **~** and you can use it in the following way: `https://my.luigi.app/home/products?~sort=asc~page=3`. - * >**NOTE:** some special characters (`<`, `>`, `"`, `'`, `/`) in node parameters are HTML-encoded. + * + * > **NOTE:** some special characters (`<`, `>`, `"`, `'`, `/`) in node parameters are HTML-encoded. * @returns {Object} node parameters, where the object property name is the node parameter name without the prefix, and its value is the value of the node parameter. For example `{sort: 'asc', page: 3}` * @memberof Lifecycle */ @@ -352,7 +353,8 @@ class LifecycleManager extends LuigiClientBase { * Returns the dynamic path parameters of the active URL. * Path parameters are defined by navigation nodes with a dynamic **pathSegment** value starting with **:**, such as **productId**. * All path parameters in the current navigation path (as defined by the active URL) are returned. - * >**NOTE:** some special characters (`<`, `>`, `"`, `'`, `/`) in path parameters are HTML-encoded. + * + * > **NOTE:** some special characters (`<`, `>`, `"`, `'`, `/`) in path parameters are HTML-encoded. * @returns {Object} path parameters, where the object property name is the path parameter name without the prefix, and its value is the actual value of the path parameter. For example ` {productId: 1234, ...}` * @memberof Lifecycle */ diff --git a/client/src/linkManager.js b/client/src/linkManager.js index 32680fc041..24a67c962d 100644 --- a/client/src/linkManager.js +++ b/client/src/linkManager.js @@ -84,8 +84,8 @@ export class linkManager extends LuigiClientBase { * @example * LuigiClient.linkManager().openAsModal('projects/pr1/users', {title:'Users', size:'m'}); */ - openAsModal(path, modalSettings) { - this.navigate(path, 0, true, modalSettings || {}); + openAsModal(path, modalSettings = {}) { + this.navigate(path, 0, true, modalSettings); } /** diff --git a/core/examples/luigi-sample-angular/README.md b/core/examples/luigi-sample-angular/README.md index a94559fae6..fcb871deb1 100644 --- a/core/examples/luigi-sample-angular/README.md +++ b/core/examples/luigi-sample-angular/README.md @@ -4,7 +4,8 @@ This is the Angular-based sample application which runs with Luigi framework. ->**NOTE:** The authorization flow in this application is a mock implementation for local development. **DO NOT USE IN PRODUCTION!** + +> **NOTE:** The authorization flow in this application is a mock implementation for local development. **DO NOT USE IN PRODUCTION!** ## Development diff --git a/core/src/utilities/helpers/routing-helpers.js b/core/src/utilities/helpers/routing-helpers.js index 836c46a516..390ee12b38 100644 --- a/core/src/utilities/helpers/routing-helpers.js +++ b/core/src/utilities/helpers/routing-helpers.js @@ -30,7 +30,17 @@ class RoutingHelpersClass { return lastElement.defaultChildNode; } else if (children && children.length) { const rootPath = pathData.navigationPath.length === 1; - if (rootPath) return children[0].pathSegment; + if (rootPath) { + const firstNodeWithPathSegment = children.find( + child => child.pathSegment + ); + return ( + (firstNodeWithPathSegment && firstNodeWithPathSegment.pathSegment) || + console.error( + 'At least one navigation node in the root hierarchy must have a pathSegment.' + ) + ); + } const validChild = children.find( child => child.pathSegment && diff --git a/docs/README.md b/docs/README.md index 1d04ba330d..3af9b1e28e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -32,9 +32,9 @@ To configure your Luigi application, simply edit the files in the `luigi-config` * [Navigation (basic)](navigation-configuration.md) - configure basic top and side navigation, links, and categories. * [Navigation (advanced)](navigation-advanced.md) - create a dynamic path, reuse micro frontends with the same origin, and configure additional navigation elements.  -* [Routing](navigation-parameters-reference.md#routing) - define routing options for your application. +* [Full parameter reference](navigation-parameters-reference.md) - find all the parameters which you can use to configure Luigi navigation in one place. * [Authorization](authorization-configuration.md) - configure login and security features for your application. - * [Authorization events](authorization-events.md) - define event configuration used to react to Luigi authorization events. +* [Authorization events](authorization-events.md) - define event configuration used to react to Luigi authorization events. * [General settings](general-settings.md) - fully customize a micro frontend, define a header, make your application responsive, and more. * [Lifecycle hooks](lifecycle-hooks.md) - execute custom logic on any of the Luigi lifecycle steps. @@ -56,11 +56,12 @@ Read the Luigi Client API documentation to learn more about the functions and pa * [UX Manager](luigi-client-api.md#uxmanager) - manage appearance options such as the behavior of backdrop or loading indicators. ## Advanced -* [Communication](communication.md) describes how to send custom messages between Luigi Core and Client. + +[Communication](communication.md) describes how to send custom messages between Luigi Core and Client. ## Examples -Check the Luigi [application examples](../core/examples/README.md) for an in-depth look at Luigi capabilities. +Check the Luigi [application examples](../core/examples) for an in-depth look at Luigi capabilities. ## Development diff --git a/docs/application-setup.md b/docs/application-setup.md index 80b22f5877..8f46d75499 100644 --- a/docs/application-setup.md +++ b/docs/application-setup.md @@ -19,10 +19,10 @@ Prior to start developing with Luigi, you need to set up your application. This Choose the framework to build your application: -[Application setup without a framework](#noframework) -[Angular 6](#angular6) -[SAPUI5/OpenUI5](#sapui5) -[VUE.JS](#vuejs) +[Application setup without a framework](#application-setup-for-an-application-not-using-a-framework) +[Angular 6](#application-setup-for-angular-6) +[SAPUI5/OpenUI5](#application-setup-for-sapui5openui5) +[VUE.JS](#application-setup-for-vuejs) ## Basic application setup @@ -46,7 +46,8 @@ The examples on this page demonstrate commands that perform each of the necessar ### Application setup for an application not using a framework ->**NOTE:** You need a development server capable of hosting Single Page Applications. The recommended server is Live Server. + +> **NOTE:** You need a development server capable of hosting Single Page Applications. The recommended server is Live Server. 1. If you do not have Live Server installed, use this command to install it. @@ -83,7 +84,8 @@ live-server --entry-file=index.html public ### Application setup for Angular 6 ->**NOTE:** The Angular CLI is a prerequisite for this example. + +> **NOTE:** The Angular CLI is a prerequisite for this example. 1. If you do not have the Angular CLI installed, download and install it from [this URL](https://cli.angular.io/). @@ -115,7 +117,8 @@ npm run start ### Application setup for SAPUI5/OpenUI5 ->**NOTE:** Live Server must be installed as your development server. + +> **NOTE:** Live Server must be installed as your development server. 1. If you do not have Live Server installed, use this command to install it. @@ -148,7 +151,8 @@ $ live-server --entry-file=index.html public ### Application setup for VUE.JS ->**NOTE:** The VUE CLI is a prerequisite for this example. + +> **NOTE:** The VUE CLI is a prerequisite for this example. 1. If you do not have VUE CLI installed, use this command to install it. diff --git a/docs/authorization-configuration.md b/docs/authorization-configuration.md index 10643c72de..e85b7fac34 100644 --- a/docs/authorization-configuration.md +++ b/docs/authorization-configuration.md @@ -15,7 +15,7 @@ meta --> # Authorization configuration -To configure authorization in Luigi, go to the `auth:` section of your project's `basicConfiguration.js` file. To see how authorization works, you can also go to the [Luigi Fiddle](https://fiddle.luigi-project.io) site and configure a sample application. +To configure authorization in Luigi, go to the `auth:` section of your Luigi configuration file. To see how authorization works, you can also go to the [Luigi Fiddle](https://fiddle.luigi-project.io) site and configure a sample application. Luigi provides OpenID Connect and OAuth2 Implicit Grant authorization out of the box. The **use** key defines the active authorization provider and the **disableAutoLogin** key allows you to disable the automatic login flow that is provided by default. @@ -46,7 +46,7 @@ auth: { openIdConnect: { authority: 'https://example.com', client_id: 'client', - scope: 'audience:server:client_id:client openid profile email groups', + scope: 'audience:server:client_id:client openID profile email groups', redirect_uri: '', post_logout_redirect_uri: '/logout.html', automaticSilentRenew: true, diff --git a/docs/authorization-events.md b/docs/authorization-events.md index 0c4d8c385e..c9a8348c4f 100644 --- a/docs/authorization-events.md +++ b/docs/authorization-events.md @@ -15,14 +15,15 @@ meta --> # Authorization events ->**NOTE:** For learning and testing purposes, use the [Luigi Fiddle](https://fiddle.luigi-project.io) page where you can configure a sample Luigi application. + +>**TIP:** For learning and testing purposes, use the [Luigi Fiddle](https://fiddle.luigi-project.io) page where you can configure a sample Luigi application. Luigi provides life cycle events which it can trigger internally or by authorization providers. Events are part of the **auth** configuration object and have to be functions. They can be executed asynchronously. An example events configuration looks as follows: -``` +```javascript auth: { events: { onAuthSuccessful: (settings, authData) => {}, @@ -45,7 +46,7 @@ You can disable the default behavior of `onAuthExpired` and `onAuthError` by mak - `onAuthSuccessful` **[function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** is executed after logging in with the **authData** object parameter. If valid authorization data was found in the local storage, the function is not executed. - `onAuthError` **[function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** is executed: - by Luigi **reason** URL parameter with optional **error** URL parameter for detailed description was found on Luigi initialization. The OAuth2Provider uses this approach by redirecting from the authorization provider to `luigi.domain/?reason=someError&error=Error detail describe`. - - by the OIDC provider if silent access token renewal fails + - by the OIDC provider if silent access token renewal fails Return `false` to prevent redirecting to `logoutUrl` after executing this function. It goes to the Luigi main route `/` instead. - `onAuthExpired` **[function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** is executed if the token expires during runtime, or if Luigi is opened with outdated authorization data in the local storage. Return `false` to prevent redirecting to `logoutUrl` after executing this function. diff --git a/docs/communication.md b/docs/communication.md index b2e44e1beb..95260ce844 100644 --- a/docs/communication.md +++ b/docs/communication.md @@ -15,7 +15,8 @@ meta --> # Communication ->**NOTE:** For learning and testing purposes, use the [Luigi Fiddle](https://fiddle.luigi-project.io) page where you can configure a sample Luigi application. + +>**TIP:** For learning and testing purposes, use the [Luigi Fiddle](https://fiddle.luigi-project.io) page where you can configure a sample Luigi application. ## Custom messages @@ -23,7 +24,7 @@ Luigi Core and Luigi Client can exchange custom messages in both directions. ### Luigi Client to Luigi Core -For Luigi Client to send messages to Luigi Core, use the [*sendCustomMessage*](luigi-client-api.md#sendCustomMessage) method from Client API. +For Luigi Client to send messages to Luigi Core, use the [sendCustomMessage](luigi-client-api.md#sendCustomMessage) method from Client API. For Luigi Core to process custom messages, define a configuration similar to the following at the root level of your Luigi configuration object: @@ -41,12 +42,12 @@ For Luigi Core to process custom messages, define a configuration similar to the } ``` where the `my-custom-message.update-top-nav` key is the message id, and the value is the listener function for the custom message. The listener receives the following input parameters: -- **customMessage** the [*message*](luigi-client-api.md#sendCustomMessage) sent by Luigi Client. +- **customMessage** the [message](luigi-client-api.md#sendCustomMessage) sent by Luigi Client. - **microfrontend** a micro frontend object as specified [here](luigi-core-api.md#getMicrofrontends). - **navigation node** a [navigation node object](navigation-parameters-reference.md#Node-parameters). ### Luigi Core to Luigi Client -For Luigi Core to send messages, use the [*customMessages*](luigi-core-api.md#customMessages) section from Core API. You can send a custom message to all rendered micro frontends, or to a specific one. For the latter, use the Core API [*elements*](luigi-core-api.md#elements) methods to retrieve micro frontends and select the one you want to send the custom message to. +For Luigi Core to send messages, use the [customMessages](luigi-core-api.md#customMessages) section from Core API. You can send a custom message to all rendered micro frontends, or to a specific one. For the latter, use the Core API [elements](luigi-core-api.md#elements) methods to retrieve micro frontends and select the one you want to send the custom message to. For Luigi Client to process the message, add and remove message listeners as described [here](luigi-client-api.md#addCustomMessageListener). diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000000..8bf1876436 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,69 @@ + + +# Frequently asked questions about Luigi + + + +### What is Luigi? + +Luigi is a micro frontend framework that helps you build modularizable, extensible, scalable and consistent UIs and web applications (for administrators and business users). + +### What are micro frontends? + +The term "micro frontends" extends the concepts of micro services to the frontend. It's an architectural style where big frontend monoliths are decomposed into smaller and simpler chunks to be developed, tested, deployed and maintained independently and rapidly (by many distributed teams), while still appearing to the customer as a one cohesive product. + +### Does Luigi deliver micro frontends? + +No, Luigi itself does not deliver any micro frontends. It is a framework that helps you develop micro frontends and connect them to web applications. + +### Where can I download Luigi? + +The Luigi project can be found on [GitHub](https://github.com/SAP/luigi). Depending on the UI framework you use, there are different setups for Luigi. You can find more information here: [application setup](application-setup.md). + + + +### The distributed development possibilities seem like a big advantage; is that just an additional benefit from using Luigi, or was that a main factor behind it? + +Development scalability was one of the main goals right from the beginning. There is a nice article on [martinfowler.com](https://martinfowler.com/articles/micro-frontends.html) explaining the benefits of a micro frontend architecture in general. All the disadvantages of the iframe approach mentioned in the article are solved with Luigi. + +### One of the potential issues with a micro frontend architecture is styling. You suggest to use the CSS elements of Fundamentals to solve that issue. Is that correct? + +It is crucial that all micro frontends in a solution follow the same design guidelines. Luigi's default UI styling is based on [Fundamentals](https://sap.github.io/fundamental-styles/) but it can be customised. If you don’t want to use Fundamentals, but Bootstrap, Material, or something else instead, you need to re-style the Luigi view components according to your design guidelines or replace them with your own components completely. + + + + + +### Luigi claims to be ‘technology agnostic’. Are you referring to the UI framework that can be used, or to some other technology? + +The main point is that you can choose any base frontend technology you prefer, such as UI5, Angular, React, or Vue. You can even mix them in one solution. One micro frontend can use UI5, while another is written in Angular and Fundamentals. The only thing that matters is that HTML/JavaScript/CSS resources are provided and served via HTTPS in the end. The fact that a micro frontend is its own web application also means that you have full freedom regarding your development toolchain and CI/CD solutions, and the web server you want to use (such as Nginx, Apache, Tomcat, or Github Pages). + +Last but not least, "technology agnostic" also means that there are no conflicts regarding any additional libraries you use, such as D3.js, Chart.js, or others. You can also avoid conflicts between different versions of the same library. Imagine a monolithic web application where a lot of teams are contributing and there has been a decision that Chart.js is the data visualization framework of choice, and then there is a need for updating the version, which potentially has breaking changes. In that case, all the teams have to be approached and asked if they use it, if their code is affected by the version update and, if so, when they can deliver the necessary changes. With Luigi, you don‘t have this management overhead at all. + +### Are there any equivalents of Luigi? + +Yes, there are several. Here are some of the most popular: [Mosaic](https://www.mosaic9.org/), [Single-spa](https://github.com/CanopyTax/single-spa), [OpenComponents](https://opencomponents.github.io/), [Piral](https://www.piral.io). Note that they are not 100% equivalents of Luigi! + + + +### Is Luigi already being used within any products, or is it still too new? + +Yes, it is already being used in production and close-to-production within SAP. For example in Kyma, SAP C/4HANA Cockpit, Context Driven Services, Konduit and Varkes. Outside of SAP, SAAS AG (partner) uses Luigi. Additionally, there are some POCs going on and we're supporting a few other customers and partners who want to start using Luigi soon. + diff --git a/docs/general-settings.md b/docs/general-settings.md index 93d986f6d7..852acaea28 100644 --- a/docs/general-settings.md +++ b/docs/general-settings.md @@ -16,7 +16,7 @@ meta --> # General settings ->**NOTE:** For learning and testing purposes, use the [Luigi Fiddle](https://fiddle.luigi-project.io) page where you can configure a sample Luigi application. +>**TIP:** For learning and testing purposes, use the [Luigi Fiddle](https://fiddle.luigi-project.io) page where you can configure a sample Luigi application. The configuration file contains a section called **Settings** in which you can configure additional Luigi options. @@ -52,12 +52,12 @@ settings: { * **header.altText** adds the HTML `alt` attribute to the logo image. * **header.title** defines the top left navigation title. * **header.favicon** defines the favicon. It requires a standard favicon file with the `.ico` extension, and 16x16px or 32x32px dimensions. -* **responsiveNavigation** allows customizing the navigation display settings. For example, you can define a button which shows or completely hides the left navigation, or a button which collapses the navigation to only show the icons. +* **responsiveNavigation** allows customizing the navigation display settings. For example, you can define a button which shows or completely hides the left navigation, or a button which collapses the navigation to only show the icons. You can set the following values: * `simple` displays the button on the left side of the top navigation regardless of the browser window´s size. - * `simpleMobileOnly` displays the button on the left side of the top navigation when the browser window is narrower than `600px`. + * `simpleMobileOnly` displays the button on the left side of the top navigation when the browser window is narrower than `600px`. * `semiCollapsible` displays the arrow button at the bottom of the left side navigation. Once you click the button, the navigation shows up or collapses.
-If you don't specify any value for **responsiveNavigation**, the buttons remain hidden. The same applies when you enable **hideSideNav** for the currently active navigation node. +If you don't specify any value for **responsiveNavigation**, the buttons remain hidden. The same applies when you enable **hideSideNav** for the currently active navigation node. * **sideNavFooterText** is a string displayed in a sticky footer inside the side navigation. It is a good place to display the version of your application. * **customTranslationImplementation** provides a custom localization implementation. It can be an Object or a Function returning an Object. This Object must provide the **getTranslation** Function as property: ```javascript @@ -67,17 +67,17 @@ If you don't specify any value for **responsiveNavigation**, the buttons remain } } ``` - + > **NOTE:** You can translate Luigi internal messages by providing translation for [these keys](../core/src/utilities/defaultLuigiTranslationTable.js). * **customSandboxRules** is an array of custom rules for the content in the iframe. You can extend the [Luigi default sandbox rules](https://github.com/SAP/luigi/blob/af1deebb392dcec6490f72576e32eb5853a894bc/core/src/utilities/helpers/iframe-helpers.js#L140) by adding further rules. -* **iframeCreationInterceptor** is a function called on iframe creation. It gives you full control over the created iframe DOM element. You can modify it to your needs just before it is added to the DOM tree. -This function is called with these parameters: +* **iframeCreationInterceptor** is a function called on iframe creation. It gives you full control over the created iframe DOM element. You can modify it to your needs just before it is added to the DOM tree. +This function is called with these parameters: * `iframe` is the iframe DOM element. It is not yet added to the DOM tree, but all attributes are already set. * `viewGroup` is the view group associated with this iframe, if applicable. - * `navigationNode` is the navigation node associated with this iframe. NOTE: the interceptor is called only once per iframe creation. If two or more navigation nodes share the same iframe (because they belong to the same view group) the interceptor is called with the first navigated node only. + * `navigationNode` is the navigation node associated with this iframe. NOTE: the interceptor is called only once per iframe creation. If two or more navigation nodes share the same iframe (because they belong to the same view group) the interceptor is called with the first navigated node only. * `microFrontendType`, which is `main`, `modal` or `split-view` depending on where it is going to be rendered. - + For example, to allow 'fullscreen' for non-modal iframes: ```javascript { diff --git a/docs/getting-started.md b/docs/getting-started.md index 5d40755fff..8684491dc5 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -59,5 +59,5 @@ Follow these steps to create a global user interface and host a full web applica Follow these steps to develop micro frontends and connect them to an already existing Luigi Core app: -1. [Install Luigi Client](https://github.com/SAP/luigi/tree/master/client#luigi-client). -2. Use the functions and parameters provided by the Luigi Client API. You can find them in the [Luigi Client documentation](https://github.com/SAP/luigi/tree/master/docs#luigi-client). \ No newline at end of file +1. [Install Luigi Client](luigi-client-setup.md). +2. Use the functions and parameters provided by the Luigi Client API. You can find them in the [Luigi Client API documentation](luigi-client-api.md). diff --git a/docs/lifecycle-hooks.md b/docs/lifecycle-hooks.md index 51aa95da34..967fc3b772 100644 --- a/docs/lifecycle-hooks.md +++ b/docs/lifecycle-hooks.md @@ -35,5 +35,5 @@ You can use any of the Luigi lifecycle hooks by adding additional setup to the r ### luigiAfterInit() -This method will be called after `Luigi.setConfig({})` is executed. +This method will be called after [Luigi.setConfig({})](luigi-core-api.md#setconfig) is executed. diff --git a/docs/luigi-client-api.md b/docs/luigi-client-api.md index e6b64c7c4d..73346256ee 100644 --- a/docs/luigi-client-api.md +++ b/docs/luigi-client-api.md @@ -1,7 +1,7 @@ + > **NOTE:** some special characters (`<`, `>`, `"`, `'`, `/`) in node parameters are HTML-encoded. Returns **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** node parameters, where the object property name is the node parameter name without the prefix, and its value is the value of the node parameter. For example `{sort: 'asc', page: 3}` @@ -133,6 +135,8 @@ Returns the dynamic path parameters of the active URL. Path parameters are defined by navigation nodes with a dynamic **pathSegment** value starting with **:**, such as **productId**. All path parameters in the current navigation path (as defined by the active URL) are returned. + + > **NOTE:** some special characters (`<`, `>`, `"`, `'`, `/`) in path parameters are HTML-encoded. Returns **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** path parameters, where the object property name is the path parameter name without the prefix, and its value is the actual value of the path parameter. For example `{productId: 1234, ...}` @@ -228,7 +232,7 @@ Opens a view in a modal. You can specify the modal's title and size. If you don' #### Parameters - `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** navigation path -- `modalSettings` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** opens a view in a modal. Use these settings to configure the modal's title and size +- `modalSettings` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** opens a view in a modal. Use these settings to configure the modal's title and size (optional, default `{}`) - `modalSettings.title` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** modal title. By default, it is the node label. If there is no label, it is left empty - `modalSettings.size` **(`"l"` \| `"m"` \| `"s"`)** size of the modal (optional, default `"l"`) diff --git a/docs/luigi-core-api.md b/docs/luigi-core-api.md index 9d48dd6887..2f39917f48 100644 --- a/docs/luigi-core-api.md +++ b/docs/luigi-core-api.md @@ -1,7 +1,7 @@ # Luigi UX features -### Rendering of Luigi application in the DOM +## Rendering of Luigi application in the DOM -By default, the Luigi content, including the top navigation, the left navigation, and the content iframed area, are rendered in the body tag of your Luigi Core application. As a result, the Luigi content takes the whole space from your browser window. +By default, Luigi content, including the top navigation, left navigation, and the content iframe area, are rendered in the body tag of your Luigi Core application. As a result, Luigi content takes the whole space of your browser window. -However, you can render the Luigi content in any other HTML container. It can be useful if you want to add a header or a footer on top of the Luigi content. To use this feature, add the `luigi-app-root` custom HTML attribute to the HTML tag in which you want to render the Luigi content. +However, you can render Luigi content in any other HTML container. It can be useful if you want to add a header or a footer on top of the Luigi content. To use this feature, add the `luigi-app-root` custom HTML attribute to the HTML tag in which you want to render the Luigi content. + >**NOTE:** If you render the Luigi content in a custom container, the container is positioned relatively when you apply your own CSS. Also, set the height of the Luigi custom container either in **px** or **vh**. -### Responsive application setup +## Responsive application setup You can quickly adjust the Luigi application to improve user experience on mobile devices, such as smartphones or tablets. Here are some examples: @@ -35,11 +36,11 @@ You can quickly adjust the Luigi application to improve user experience on mobil ``` -* Define and apply [responsiveNavigation](general-settings.md) settings to make the left navigation responsive. +* Define and apply [**responsiveNavigation**](general-settings.md) settings to make the left navigation responsive. ### App loading indicator -To show a loading indicator before Luigi Core or your first micro frontend is ready, add a container with the `luigi-app-loading-indicator` attribute to your _index.html_ body or inside your [`luigi-app-root`](#rendering-of-luigi-application-in-the-dom) container. +To show a loading indicator before Luigi Core or your first micro frontend is ready, add a container with the `luigi-app-loading-indicator` attribute to your `index.html` body or inside your `luigi-app-root` container. ```html
@@ -49,11 +50,11 @@ To show a loading indicator before Luigi Core or your first micro frontend is re
``` -By default, the loading indicator is removed after `Luigi.setConfig({})` has been executed. +By default, the loading indicator is removed after [Luigi.setConfig({})](luigi-core-api.md#setconfig) has been executed. Alternatively, to keep the loading indicator until the first micro frontend is usable, follow these steps: -1. Set the app loading indicator parameter `hideAutomatically` to `false` +1. Set the app loading indicator parameter **hideAutomatically** to `false` ```javascript { @@ -66,4 +67,4 @@ Alternatively, to keep the loading indicator until the first micro frontend is u ... } ``` -2. Call [`Luigi.ux().hideAppLoadingIndicator()`](./luigi-core-api.md#hideAppLoadingIndicator) in the Luigi Core once your initial micro frontend has finished loading to remove the loading indicator. You can, for example, use the [custom messages](./communication.md#custom-messages) feature to allow the Luigi Client micro frontend to communicate with the Core when this function should be executed. +2. To remove the loading indicator, call [Luigi.ux().hideAppLoadingIndicator()](./luigi-core-api.md#hideAppLoadingIndicator) in Luigi Core once your initial micro frontend has finished loading. You can, for example, use the [custom messages](./communication.md#custom-messages) feature to allow the Luigi Client micro frontend to communicate with the Core when this function should be executed. diff --git a/docs/navigation-advanced.md b/docs/navigation-advanced.md index 8943e186b6..a4b3f4402e 100644 --- a/docs/navigation-advanced.md +++ b/docs/navigation-advanced.md @@ -337,6 +337,7 @@ appSwitcher = { For more options and parameters which you can use to configure navigation in Luigi, read the [full parameter reference](navigation-parameters-reference.md). Some of the topics you can find there include: +* Defining the [routing](navigation-parameters-reference.md#routing-parameters) strategy of your application * Enabling and disabling the [loading indicator](navigation-parameters-reference.md#loadingindicatorenabled) * Hiding [navigation nodes](navigation-parameters-reference.md#hidefromnav) or [side navigation](navigation-parameters-reference.md#hidesidenav) * Including a horizontal [tab navigation](navigation-parameters-reference.md#tabnav) bar diff --git a/docs/navigation-configuration.md b/docs/navigation-configuration.md index 5d8c7d7983..fab7c0b20c 100644 --- a/docs/navigation-configuration.md +++ b/docs/navigation-configuration.md @@ -87,6 +87,7 @@ navigation: { ## Basic navigation parameters + >**NOTE:** For a full list of available parameters, see the [parameter reference](navigation-parameters-reference.md) document. The [first steps](#first-steps) example provides some basic navigation parameters: diff --git a/docs/navigation-parameters-reference.md b/docs/navigation-parameters-reference.md index 3eda87a1f1..07e78f221a 100644 --- a/docs/navigation-parameters-reference.md +++ b/docs/navigation-parameters-reference.md @@ -1,7 +1,7 @@ # Navigation parameters reference -> **NOTE:** To see the navigation parameters in use, see the [navigation configuration example](navigation-configuration-example.md) or configure a test application in the [Luigi Fiddle](https://fiddle.luigi-project.io). +> **TIP:** To see the navigation parameters in use, see the [navigation configuration example](navigation-configuration-example.md) or configure a test application in the [Luigi Fiddle](https://fiddle.luigi-project.io). Use the parameters and functions in this reference to configure your Luigi navigation structure. @@ -65,7 +65,7 @@ The node navigation parameters allow you to configure global navigation settings ### preloadViewGroups - **type**: boolean -- **description**: allows deactivating the default preloading of [view groups](navigation-configuration.md#view-groups) iframes. +- **description**: allows deactivating the default preloading of [view groups](navigation-advanced.md#view-groups) iframes. ### viewGroupsSettings - **type**: object @@ -83,7 +83,7 @@ Node parameters are all the parameters that can be added to an individual naviga - **description**: specifies the partial URL of the current segment. **pathSegment** must not contain slashes. - **examples**: - A static **pathSegment** of value `settings` results in `example.com/settings`. - - A dynamic **pathSegment** is prefixed with a colon and can load any value. Find out more about dynamic paths in Luigi [here](navigation-configuration.md#creating-a-dynamic-path). + - A dynamic **pathSegment** is prefixed with a colon and can load any value. Find out more about dynamic paths in Luigi [here](navigation-advanced.md#dynamically-changeable-paths). ### link - **type**: string @@ -112,9 +112,9 @@ Node parameters are all the parameters that can be added to an individual naviga ### viewUrl - **type**: string - **description**: contains the URL or path to a view which renders when you click the navigation node. Use either a full URL or a relative path. If **viewUrl** is undefined, Luigi activates the child node specified in **defaultChildNode**. When both **viewUrl** and **defaultChildNode** are undefined, Luigi opens the first child of the current node. **viewUrl** can contain variables from: -* dynamic path segments -* node parameters -* contexts + * dynamic path segments + * node parameters + * contexts ### navigationContext @@ -129,6 +129,31 @@ Node parameters are all the parameters that can be added to an individual naviga - **type**: string - **description**: sets the child node that Luigi activates automatically if the current node has no **viewUrl** defined. Provide **pathSegment** of the child node you want to activate as a string. + +>**NOTE:** To define a root-level **defaultChildNode** which is different than the first header navigation node, set an object with **defaultChildNode** and **children** set to `navigation.nodes` instead of an array of nodes. + +- **example**: +```javascript +settings: { + navigation: { + nodes: { + defaultChildNode: 'overview', + children: [ + { + pathSegment: 'docs', + label: 'Documentation', + viewUrl: '...' + }, + { + pathSegment: 'overview', + label: 'Overview', + viewUrl: '...' + } + ] + } + } +``` + ### isolateView - **type**: boolean - **description**: renders the view in a new frame when you enter and leave the node. This setting overrides the same-domain frame re-usage. The **isolateView** is disabled by default. @@ -145,20 +170,20 @@ Node parameters are all the parameters that can be added to an individual naviga - **type**: boolean - **description**: shows a loading indicator when switching between micro frontends. If you have a fast micro frontend, you can disable this feature to prevent flickering of the loading indicator. This parameter is enabled by default. - **example**: -```javascript -loadingIndicator: { - enabled: true -} -``` + ```javascript + loadingIndicator: { + enabled: true + } + ``` ### loadingIndicator.hideAutomatically - **type**: boolean - **description**: if set to `false`, it disables the automatic hiding of the loading indicator once the micro frontend is loaded. It is only considered if the loading indicator is enabled. It does not apply if the loading indicator is activated manually with the `LuigiClient.uxManager().showLoadingIndicator()` function. If the loading indicator is enabled and automatic hiding is disabled, use `LuigiClient.uxManager().hideLoadingIndicator()` to hide it manually in your micro frontend during the startup. This parameter is enabled by default. - **example**: -```javascript -loadingIndicator: { - hideAutomatically: false -} -``` + ```javascript + loadingIndicator: { + hideAutomatically: false + } + ``` ### icon - **type**: string - **description**: the name of an icon, without the `sap-icon--` prefix. Its source may be [OpenUI](https://openui5.hana.ondemand.com/1.40.10/iconExplorer.html) or a custom link (relative or absolute) to an image. The icon is displayed next to the node label in the side navigation or instead of the label in the top navigation. @@ -204,11 +229,11 @@ loadingIndicator: { - **type**: boolean - **description**: current locale can be changed from client using the corresponding API if this is set to `true` - **example**: -```javascript - clientPermissions: { - changeCurrentLocale: true - } -``` + ```javascript + clientPermissions: { + changeCurrentLocale: true + } + ``` ### tabNav - **type**: boolean @@ -292,7 +317,8 @@ The profile section is a configurable drop-down list available in the top naviga - **type**: function - **description**: used to retrieve a user's name and email to simulate logging in. It can be used when authorization is disabled and also gets called if the defined IDP provider does not have **settings.userInfoFn** defined or does not provide a `userInfo` function internally. It can be asynchronous and should return an object with **name**, **email** and **picture** parameters. ->**NOTE:** Neither authorization nor profile parameter are configured if the profile section in the top navigation bar is not visible. + +>**NOTE:** Neither **authorization** nor **profile** parameter is configured if the profile section in the top navigation bar is not visible. ## Product switcher diff --git a/website/docs/.gitignore b/website/docs/.gitignore index 823e714a68..b35e3030d0 100644 --- a/website/docs/.gitignore +++ b/website/docs/.gitignore @@ -7,7 +7,6 @@ # Generated and copied extendedConfiguration.bundle.js -fundamental-ui luigi-core luigi-client public/* diff --git a/website/docs/package-lock.json b/website/docs/package-lock.json index aac5e831b0..9cfcec9dba 100644 --- a/website/docs/package-lock.json +++ b/website/docs/package-lock.json @@ -3075,12 +3075,6 @@ "resolve-dir": "^1.0.1" } }, - "fiori-fundamentals": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/fiori-fundamentals/-/fiori-fundamentals-1.7.1.tgz", - "integrity": "sha512-t7pyojGOUmzENxMi88VBwA0gAHoEl6s3ImHLkwDj9acWhgQBSPXewUSvCQQGmRFDuCCmcc42zI62PG7Ql/Hyrg==", - "dev": true - }, "flush-write-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", diff --git a/website/docs/package.json b/website/docs/package.json index 45f2718fc4..3639cdb8c9 100644 --- a/website/docs/package.json +++ b/website/docs/package.json @@ -43,7 +43,6 @@ "copy-webpack-plugin": "^5.0.4", "css-loader": "^3.2.0", "esm": "^3.2.25", - "fiori-fundamentals": "^1.7.1", "lodash.orderby": "^4.6.0", "mini-css-extract-plugin": "^0.8.0", "node-sass": "^4.12.0", diff --git a/website/docs/public/index.html b/website/docs/public/index.html index 4eedaac360..3f7b3484d6 100644 --- a/website/docs/public/index.html +++ b/website/docs/public/index.html @@ -1,12 +1,13 @@ - Luigi Core + Documentation - Luigi - The Enterprise-Ready Micro Frontend Framework + diff --git a/website/docs/scripts/markdown.unified-preview.js b/website/docs/scripts/markdown.unified-preview.js index 52d13b807f..1cd477988f 100644 --- a/website/docs/scripts/markdown.unified-preview.js +++ b/website/docs/scripts/markdown.unified-preview.js @@ -7,9 +7,10 @@ const filesToProcess = [ // 'link.md', // 'codeblocks.md', // 'custom-attributes.md', - 'frontmatter-3.md', - 'frontmatter-2.md', - 'frontmatter.md' + 'custom-wrappers.md', + // 'frontmatter-3.md', + // 'frontmatter-2.md', + // 'frontmatter.md' ]; filesToProcess.forEach(async (name) => { diff --git a/website/docs/scripts/mocks/custom-wrappers.md b/website/docs/scripts/mocks/custom-wrappers.md new file mode 100644 index 0000000000..203ea6ac9a --- /dev/null +++ b/website/docs/scripts/mocks/custom-wrappers.md @@ -0,0 +1,38 @@ +# Frequently asked questions + +Here we have some answers to your questions + + + +### This is the Accordion Headline + +This is the text +This is the text +This is the text +This is the text + +``` +Sample code +``` +### Whats next? + +Everything fine! + + + +And now an accordion with all items openend + + + + +### Another Section + +This is the text +This is the text + +### Another Section + +This is the text +This is the text + + \ No newline at end of file diff --git a/website/docs/src/client-js/accordion.js b/website/docs/src/client-js/accordion.js new file mode 100644 index 0000000000..f0f160086f --- /dev/null +++ b/website/docs/src/client-js/accordion.js @@ -0,0 +1,8 @@ +export class Accordion { + init() { + window.accordionToggle = (event, element) => { + event.preventDefault(); + element.parentNode.classList.toggle('active'); + }; + } +} \ No newline at end of file diff --git a/website/docs/src/client-js/copy-code.js b/website/docs/src/client-js/copy-code.js new file mode 100644 index 0000000000..0487a162f8 --- /dev/null +++ b/website/docs/src/client-js/copy-code.js @@ -0,0 +1,30 @@ +export class CopyCodeHandler { + init() { + window.copyCode = (evt, elem) => { + evt.preventDefault(); + evt.stopPropagation(); + try { + this.selectText(elem.parentNode.querySelector('code')); + document.execCommand('copy'); + } catch(e) { + console.error('Browser copy command not supported?', e); + } + } + } + + selectText(node) { + if (document.body.createTextRange) { + const range = document.body.createTextRange(); + range.moveToElementText(node); + range.select(); + } else if (window.getSelection) { + const selection = window.getSelection(); + const range = document.createRange(); + range.selectNodeContents(node); + selection.removeAllRanges(); + selection.addRange(range); + } else { + console.warn("Could not select text in node: Unsupported browser."); + } + } +} \ No newline at end of file diff --git a/website/docs/src/client-js/internal-links.js b/website/docs/src/client-js/internal-links.js new file mode 100644 index 0000000000..54c8d82508 --- /dev/null +++ b/website/docs/src/client-js/internal-links.js @@ -0,0 +1,53 @@ +import LuigiClient from '@kyma-project/luigi-client'; + +export class InternalLinksHandler { + init() { + LuigiClient.addInitListener((ctx) => { + if(this.initDone) { return; } + this.initDone = true; + + // modify internal links to be valid links for users and still make sapper happy + // since leaving them "wrong" (as local iframe links) in the first place + let intvCount = 0; + const intv = setInterval(() => { + const links = document.querySelectorAll('a[data-linktype]'); + intvCount++; + if (links.length) { + this.prepareLinks(ctx, links); + } + if (links.length || intvCount >= 20) { + clearInterval(intv); + } + }, 150); + + + // register click handler + window.navigateInternal = (evt, elem) => { + evt.preventDefault(); + evt.stopPropagation(); + let url; + try { + url = new URL(elem.getAttribute('href')); + } catch (error) { + console.debug('navigateInternal URL parse error', elem, elem.getAttribute('href'), error); + } + const urlWithPath = url.pathname.replace(ctx.coreBaseUrl, '').replace('.md', '').replace('/docu-microfrontend', ''); + if (url.hash) { + LuigiClient.linkManager().withParams({hash: url.hash.toLowerCase()}).navigate(urlWithPath); + } else { + LuigiClient.linkManager().navigate(urlWithPath); + } + } + }); + } + + prepareLinks(ctx, links) { + links.forEach((link) => { + if (link.getAttribute('data-linktype') === 'internal') { + const url = new URL(link.href); + let newHref = ctx.coreBaseUrl + url.pathname.replace('.md', '').replace('/docu-microfrontend', '') + url.hash.toLowerCase(); + link.setAttribute('href', newHref); + } + }); + } +} diff --git a/website/docs/src/client-js/smooth-scroll-anchors.js b/website/docs/src/client-js/smooth-scroll-anchors.js new file mode 100644 index 0000000000..36afc83792 --- /dev/null +++ b/website/docs/src/client-js/smooth-scroll-anchors.js @@ -0,0 +1,36 @@ +import LuigiClient from '@kyma-project/luigi-client'; + + +export class ScrollAnchorsHandler { + init() { + // scroll if navigate param found + LuigiClient.addInitListener((ctx) => { + if(this.initDone) { return; } + this.initDone = true; + if (LuigiClient.getNodeParams().hash) { + this.scrollAnchor(null, LuigiClient.getNodeParams().hash); + } + window.scrollAnchor = this.scrollAnchor; + }); + } + + // Vanilla JavaScript Scroll to Anchor + // @ https://perishablepress.com/vanilla-javascript-scroll-anchor/ + // modified so respond could be also just a string with the #hash + scrollAnchor(e, respond = null) { + let targetID; + if (e) { + e.preventDefault(); + targetID = (respond) ? respond.getAttribute('href') : this.getAttribute('href'); + targetID = `#${targetID.split('#').pop()}`; + } else { + targetID = respond; + } + + const targetAnchor = document.querySelector(targetID); + if (!targetAnchor) return; + targetAnchor.scrollIntoView({ + behavior: 'smooth' + }); + } +} \ No newline at end of file diff --git a/website/docs/src/client.js b/website/docs/src/client.js index 51dc47ce92..54879d9da4 100644 --- a/website/docs/src/client.js +++ b/website/docs/src/client.js @@ -1,51 +1,17 @@ import * as sapper from '@sapper/app'; -import LuigiClient from '@kyma-project/luigi-client'; + +import { CopyCodeHandler } from './client-js/copy-code'; +import { InternalLinksHandler } from './client-js/internal-links'; +import { ScrollAnchorsHandler } from './client-js/smooth-scroll-anchors'; +import { Accordion } from './client-js/accordion'; sapper.start({ target: document.querySelector('#sapper') }); -const selectText = (node) => { - if (document.body.createTextRange) { - const range = document.body.createTextRange(); - range.moveToElementText(node); - range.select(); - } else if (window.getSelection) { - const selection = window.getSelection(); - const range = document.createRange(); - range.selectNodeContents(node); - selection.removeAllRanges(); - selection.addRange(range); - } else { - console.warn("Could not select text in node: Unsupported browser."); - } -} - -window.copyCode = (evt, elem) => { - evt.preventDefault(); - evt.stopPropagation(); - try { - selectText(elem.parentNode.querySelector('code')); - document.execCommand('copy'); - } catch(e) { - console.error('Browser copy command not supported?', e); - } -} +new CopyCodeHandler().init(); +new InternalLinksHandler().init(); +new Accordion().init(); -LuigiClient.addInitListener((ctx) => { - const links = document.querySelectorAll('a[data-linktype]'); - if (links) { - links.forEach((link, index) => { - if (link.getAttribute('data-linktype') === 'internal') { - const url = new URL(link.href); - link.setAttribute('href', ctx.coreBaseUrl + url.pathname.replace('.md', '').replace('/docu-microfrontend', '')); - } - }); - } +new ScrollAnchorsHandler().init(); - window.navigateInternal = (evt, elem) => { - evt.preventDefault(); - evt.stopPropagation(); - LuigiClient.linkManager().navigate(elem.getAttribute('href').replace(ctx.coreBaseUrl, '').replace('.md', '').replace('/docu-microfrontend', '')); - } -}); diff --git a/website/docs/src/docs/general-settings.md b/website/docs/src/docs/general-settings.md deleted file mode 100644 index 50a2c9453c..0000000000 --- a/website/docs/src/docs/general-settings.md +++ /dev/null @@ -1,52 +0,0 @@ -# General settings - -The configuration file contains a section called **Settings** in which you can configure additional Luigi options. - -```javascript -settings: { - hideNavigation: false - backdropDisabled : false, - header: { object / function / Promise - logo: 'path/to/image.png', - title: 'Luigi Demo', - favicon: 'path/to/favicon.ico' - }, - sideNavFooterText: 'MyLovelyApp 1.0.0', - customTranslationImplementation: () => { - return { - getTranslation: (key, interpolations, locale) => { - return translatedText; - } - }; - }, - customSandboxRules: ['allow-downloads-without-user-activation'], - appLoadingIndicator: { - hideAutomatically: true - } -} -``` - -* **hideNavigation** disables Luigi's default out-of-the-box navigation when set to `true`. As a result, neither the left nor top navigation is visible in your application, and you can implement your own navigation UI. By default, the parameter is set to `false`, which means the navigation is enabled. -* **backdropDisabled** prevents the backdrop layer from covering the top and left navigation when showing modal windows. By default, the backdrop is set to `true`. -* **header.logo** defines the top left navigation logo. It has a fixed height of 28px. -* **header.title** defines the top left navigation title. -* **header.favicon** defines the favicon. It requires a standard favicon file with the `.ico` extension, and 16x16px or 32x32px dimensions. -* **responsiveNavigation** allows customizing the navigation display settings. For example, you can define a button which shows or completely hides the left navigation, or a button which collapses the navigation to only show the icons. -You can set the following values: - * `simple` displays the button on the left side of the top navigation regardless of the browser window´s size. - * `simpleMobileOnly` displays the button on the left side of the top navigation when the browser window is narrower than `600px`. - * `semiCollapsible` displays the arrow button at the bottom of the left side navigation. Once you click the button, the navigation shows up or collapses.
-If you don't specify any value for **responsiveNavigation**, the buttons remain hidden. The same applies when you enable **hideSideNav** for the currently active navigation node. -* **sideNavFooterText** is a string displayed in a sticky footer inside the side navigation. It is a good place to display the version of your application. -* **customTranslationImplementation** provides a custom localization implementation. It can be an Object or a Function returning an Object. This Object must provide the **getTranslation** Function as property: -```javascript -{ - getTranslation: (key, interpolations, locale) => { - // should return translation of the 'key' in the 'locale' or current locale - } -} -``` - -> **NOTE:** You can translate Luigi internal messages by providing translation for [these keys](https://github.com/SAP/luigi/blob/master/core/src/utilities/defaultLuigiTranslationTable.js). -* **customSandboxRules** is an array of custom rules for the content in the iframe. You can extend the [Luigi default sandbox rules](https://github.com/SAP/luigi/blob/af1deebb392dcec6490f72576e32eb5853a894bc/core/src/utilities/helpers/iframe-helpers.js#L140) by adding further rules. -* **appLoadingIndicator.hideAutomatically** allows you to disable automatic hiding of the app loading indicator, which is enabled by default in case the app loading indicator is being used. Take a look at the [App loading indicator](luigi-ux-features.md#app-loading-indicator) section on how to use this feature. diff --git a/website/docs/src/luigi-config/extended/settings.js b/website/docs/src/luigi-config/extended/settings.js index 415d53948f..9ff956616f 100644 --- a/website/docs/src/luigi-config/extended/settings.js +++ b/website/docs/src/luigi-config/extended/settings.js @@ -7,7 +7,7 @@ class Settings { }; responsiveNavigation = 'simpleMobileOnly'; // Options: simple | simpleMobileOnly | semiCollapsible - sideNavFooterText = 'Copyright 2019.'; + sideNavFooterText = ' '; // hideNavigation = true // backdropDisabled = true } diff --git a/website/docs/src/luigi-config/styles/index.scss b/website/docs/src/luigi-config/styles/index.scss index c76ac8dd1b..f1ccf3bf69 100644 --- a/website/docs/src/luigi-config/styles/index.scss +++ b/website/docs/src/luigi-config/styles/index.scss @@ -313,18 +313,34 @@ $side-nav-width: 260px; .lui-side-nav__footer { background-color: $secondary-color; color: white; + min-height: 58px; + + a { + color: white; + display: inline-block; + margin-right: 20px; + + &:last-child { + margin-right: 0; + } + + &:hover, + &:active { + color: $primary-color; + } + } .lui-side-nav__footer--text { width: 100%; color: white; font-size: 14px; font-weight: 600; - text-align: center; - padding: 20px; - &:before { - content: '\00a9'; - margin-right: 5px; - } + padding: 20px 40px; + + // &:before { + // content: '\00a9'; + // margin-right: 5px; + // } } } @@ -402,7 +418,7 @@ $side-nav-width: 260px; padding: 30px; @media screen and (min-width: 1024px) { - padding: 93px 40px 0; + padding: 50px 40px 0; } } diff --git a/website/docs/src/routes/_layout.svelte b/website/docs/src/routes/_layout.svelte index d66d8af5ce..632c51c388 100644 --- a/website/docs/src/routes/_layout.svelte +++ b/website/docs/src/routes/_layout.svelte @@ -11,6 +11,7 @@ $light-gray: #e6e6e6; $medium-gray: #cacaca; $powder-blue: #d0d8e2; + $ice-blue: #edf2f7; $side-nav-width: 320px; @@ -29,15 +30,14 @@ color: $secondary-color; box-sizing: border-box; - - .docu-content { padding: 30px; font-size: 16px; + max-width: 1500px; @media screen and (min-width: (1024px - $side-nav-width)) { - padding: 95px 80px; + padding: 50px 80px; } } @@ -403,6 +403,72 @@ border-radius: 3px; font-size: 14px; } + + .accordion-container { + + margin: 30px 0; + + @media screen and (min-width: (1024px - $side-nav-width)) { + margin: 50px 0; + } + + .accordion-item { + + margin-bottom: 12px; + border: 1px solid $powder-blue; + position: relative; + + &:after { + display: block; + position: absolute; + top: 18px; + right: 21px; + content: '+'; + color: $primary-color; + font-size: 28px; + } + + &.active { + .accordion-item-content { + display: block; + } + + .accordion-item-title { + background-color: $ice-blue; + } + + &:after { + top: 16px; + content: '\2013'; + } + } + } + + .accordion-item-title { + padding: 18px 80px 18px 20px; + cursor: pointer; + + h3 { + font-size: 16px; + line-height: 28px; + margin: 0; + font-weight: 600; + } + } + + .accordion-item-content { + display: none; + margin: 0; + padding: 18px 20px; + + p { + + &:last-child { + margin-bottom: 0; + } + } + } + } } diff --git a/website/docs/src/routes/docs/index.svelte b/website/docs/src/routes/docs/index.svelte index f8d6c4dc59..575afd8dc8 100644 --- a/website/docs/src/routes/docs/index.svelte +++ b/website/docs/src/routes/docs/index.svelte @@ -12,7 +12,10 @@ @@ -20,13 +23,10 @@ {#each docs as doc} - {#if doc.shortName == "README"} {@html doc.contents} {:else} -
  • {doc.name}
  • + + {/if} {/each} diff --git a/website/docs/src/services/markdown.service.js b/website/docs/src/services/markdown.service.js index 1f59fa2db9..f987f2d8f3 100644 --- a/website/docs/src/services/markdown.service.js +++ b/website/docs/src/services/markdown.service.js @@ -10,6 +10,7 @@ import frontmatter from 'remark-frontmatter'; import luigiLinkParser from '../unified-plugins/rehype-luigi-linkparser'; import addCopyToClipboard from '../unified-plugins/rehype-copy-to-clipboard'; import addCustomAttributes from '../unified-plugins/rehype-add-custom-attributes'; +import wrapAccordion from '../unified-plugins/rehype-accordion'; import luigiNavigationBuilder from '../unified-plugins/remark-generate-luigi-navigation'; // import highlight from 'rehype-highlight' // syntax highlight code blocks with lowlight: https://github.com/wooorm/lowlight @@ -27,10 +28,11 @@ class MarkdownService { .use(remark2rehype, {allowDangerousHTML: true}) .use(raw) .use(addCustomAttributes) + .use(wrapAccordion, { questionTagName: 'h3' }) .use(luigiLinkParser, data) - .use(section) .use(addIdsToHeadings) .use(addCopyToClipboard) + .use(section) // section should be the last one .use(format) .use(html) .process(String(value), function (err, file) { diff --git a/website/docs/src/unified-plugins/rehype-accordion.js b/website/docs/src/unified-plugins/rehype-accordion.js new file mode 100644 index 0000000000..02fa7a912c --- /dev/null +++ b/website/docs/src/unified-plugins/rehype-accordion.js @@ -0,0 +1,106 @@ +import h from 'hastscript'; +// import hastFromParse5 from 'hast-util-from-parse5'; +// import hastToHTML from 'hast-util-to-html'; +// import parse5 from 'parse5'; + +export default function wrapAccordion(options = { questionTagName: 'h3' }) { + let settings = getDefaultSettings(); + let isAccordion = false; + let accordion; + let currentQuestion; + return function transformer(tree) { + tree.children.forEach(node => { + processComment(node); + isAccordion && processAccordionElements(node); + }); + } + + function getDefaultSettings() { + return Object.assign({}, { + defaultState: '' + }); + } + + function beginQuestion(node) { + currentQuestion = h('div.accordion-item', { class: settings.defaultState }, [ + h('dt.accordion-item-title', + { onclick: 'accordionToggle(event, this)' }, Object.assign({}, node)), + h('dd.accordion-item-content', []), + ]); + } + + function finishQuestion() { + // end of question, push current question to accordion + accordion.children.push(currentQuestion); + currentQuestion = undefined; + } + + function clearNode(node) { + node.type = 'text', + node.value = ''; + node.children = []; + } + + function storeSettings(str) { + const keyValues = str.split(' '); + const options = keyValues.map(kv => ({ key: kv.split(':')[0], value: kv.split(':')[1] })); + for (const opt of options) { + settings[opt.key] = opt.value; + } + } + + function processAccordionElements(node) { + if(currentQuestion && node.tagName === options.questionTagName) { + finishQuestion(); + } + + if(!currentQuestion && node.tagName === options.questionTagName) { + beginQuestion(node); + } else if(currentQuestion) { + // push answer line to item-answer container + currentQuestion.children[1].children.push(Object.assign({}, node)); + } + clearNode(node); + } + + function processComment(node) { + if (node.type === 'comment') { + if (node.value.trim().startsWith('accordion:start')) { + isAccordion = true; + storeSettings(node.value.trim()); + + // start accordion section + accordion = h('dl.accordion-container', [ + // all accordion-items will be pushed here + ]); + } + + if (node.value.trim().startsWith('accordion:end')) { + // reset + isAccordion = false; + settings = getDefaultSettings(); + + if(currentQuestion) { + finishQuestion(); + } + // write accordion section and remove accordion + Object.assign(node, accordion); + } + } + } + + // function wrapHtml(node) { + // let nodeHTML = hastToHTML(node); + // console.log('node before', nodeHTML); + // modifiers.forEach((tpl) => { + // nodeHTML = tpl.replace(textMarker, nodeHTML); + // }); + // // clear attributes for next run + // modifiers = []; + + // const htmlAST = parse5.parse(String(nodeHTML), {sourceCodeLocationInfo: false}) + // const newNode = hastFromParse5(htmlAST, {file: nodeHTML}).children[0]; + + // Object.assign(node, newNode); + // } +} \ No newline at end of file diff --git a/website/docs/src/unified-plugins/rehype-luigi-linkparser.js b/website/docs/src/unified-plugins/rehype-luigi-linkparser.js index 415981769a..287f4b46fb 100644 --- a/website/docs/src/unified-plugins/rehype-luigi-linkparser.js +++ b/website/docs/src/unified-plugins/rehype-luigi-linkparser.js @@ -41,16 +41,16 @@ export default function luigiLinkParser(options) { if(newHref.startsWith('./')) { newHref = newHref.substr(2); } - const newUrl = url.parse(prependForExport() + '/docs/' + newHref); - // parsed.href does not work currently with # anchor link links - node.properties['href'] = newUrl.pathname; + + node.properties['href'] = prependForExport() + '/docs/' + newHref; } else if (parsed.protocol) { // external link node.properties['rel'] = 'external'; node.properties['target'] = '_blank'; } else if (parsed.hash && !parsed.pathname && !parsed.hostname) { // current page anchor link - node.properties['href'] = prependForExport() + '/docs/' + settings.shortName + parsed.hash; + node.properties['href'] = prependForExport() + '/docs/' + settings.shortName + parsed.hash.toLowerCase(); + node.properties['onclick'] = 'scrollAnchor(event, event.target)'; } else if (parsed.pathname && ( parsed.pathname.startsWith('../') || parsed.pathname.startsWith('/') )) { diff --git a/website/docs/static/luigi/index.html b/website/docs/static/luigi/index.html index 4eedaac360..0a0f491741 100644 --- a/website/docs/static/luigi/index.html +++ b/website/docs/static/luigi/index.html @@ -1,12 +1,13 @@ - Luigi Core + Documentation - Luigi - The Enterprise-Ready Micro Frontend Framework + @@ -14,5 +15,6 @@ + diff --git a/website/docs/static/luigi/privacy-policy.js b/website/docs/static/luigi/privacy-policy.js new file mode 100644 index 0000000000..cf8a9b429a --- /dev/null +++ b/website/docs/static/luigi/privacy-policy.js @@ -0,0 +1,23 @@ +(function() { + setTimeout(function(){ + try { + let aPrivacyPolicy = document.createElement('a'); + const linkTextPrivacyPolicy = document.createTextNode("Privacy Policy"); + aPrivacyPolicy.appendChild(linkTextPrivacyPolicy); + aPrivacyPolicy.href = "https://www.sap.com/about/legal/privacy.html"; + aPrivacyPolicy.target = "_blank"; + const parent = document.getElementsByClassName("lui-side-nav__footer--text")[0]; + const child = document.getElementsByClassName("lui-side-nav__footer--text")[0].firstChild; + parent.removeChild(child); + document.getElementsByClassName("lui-side-nav__footer--text")[0].appendChild(aPrivacyPolicy); + let aLegal = document.createElement('a'); + const linkTextLegal = document.createTextNode("Legal"); + aLegal.appendChild(linkTextLegal); + aLegal.href = "https://www.sap.com/about/legal/impressum.html"; + aLegal.target = "_blank"; + document.getElementsByClassName("lui-side-nav__footer--text")[0].appendChild(aLegal); + } catch(e) { + console.error('Something went wrong!', e); + } + }, 1000); +})(); \ No newline at end of file diff --git a/website/docs/webpack-generateConfig.config.js b/website/docs/webpack-generateConfig.config.js index 6eb7c2c46f..7fa5c356c1 100644 --- a/website/docs/webpack-generateConfig.config.js +++ b/website/docs/webpack-generateConfig.config.js @@ -26,10 +26,6 @@ module.exports = { "from": "node_modules/@kyma-project/luigi-client", "to": "../luigi-client" }, - { - "from": "node_modules/fiori-fundamentals/dist", - "to": "../fundamental-ui" - }, { "from": "../../docs/assets", "to": "../assets"