Skip to content

Commit

Permalink
chore: wip
Browse files Browse the repository at this point in the history
  • Loading branch information
blackfalcon committed Jan 15, 2025
1 parent 92a7c81 commit 348358f
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 14 deletions.
53 changes: 53 additions & 0 deletions apiExamples/dynamichostname.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<p>
Expect this
<auro-hyperlink
href="https://www.alaskaair.com/faq.html">
Alaska Airlines
</auro-hyperlink>
link to be redefined based on the hostname of the current page.
</p>
<p>
Expect this
<auro-hyperlink
origin="hostname"
href="https://www.alaskaair.com/faq.html">
Alaska Airlines
</auro-hyperlink>
link to be maintain the hostname of the original link.
</p>
<p>
Expect this
<auro-hyperlink
target="_blank"
href="https://www.alaskaair.com/faq.html">
Alaska Airlines
</auro-hyperlink>
link to be maintain the hostname of the original link.
</p>
<p>
Expect this
<auro-hyperlink
origin="dynamic"
target="_blank"
href="https://www.alaskaair.com/faq.html">
Alaska Airlines
</auro-hyperlink>
link to be redefined based on the hostname of the current page.
</p>
<p>
Expect this
<auro-hyperlink
href="/faq.html">
Alaska Airlines
</auro-hyperlink>
link to be redefined based on the hostname of the current page.
</p>
<p>
Expect this
<auro-hyperlink
relative
href="/faq.html">
Alaska Airlines
</auro-hyperlink>
link to be remain relative.
</p>
1 change: 1 addition & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
| `fluid` | `fluid` | | `Boolean` | | If true and `type="cta"`, the hyperlink will have a fluid-width UI. |
| `href` | `href` | | `String` | | Defines the URL of the linked page. |
| `ondark` | `ondark` | | `Boolean` | false | If true, the hyperlink will be styled for use on a dark background. |
| `origin` | `origin` | | `String` | | Defines hostname for the origin of the URL. Options are `hostname` or `dynamic`. Default is `dynamic`. Using `hostname` will force the URL to be rewritten to use the hostname of the `href` attribute. Using `dynamic` will rewrite the URL to use the hostname of the current page. |
| `referrerpolicy` | `referrerpolicy` | | `Boolean` | | If true, sets `strict-origin-when-cross-origin` to control the referrer information sent with requests. |
| `rel` | `rel` | | `String` | | Defines the relationship between the current document and the linked document. |
| `relative` | `relative` | | `Boolean` | false | If true, the auto URL re-write feature will be disabled. |
Expand Down
17 changes: 17 additions & 0 deletions docs/partials/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,23 @@

</auro-accordion>

## Dynamic hostname support

The `auro-hyperlink` component defaults to modifying the origin of any provided URL to match the hostname of the current page unless explicitly configured. Using `origin="hostname"` will retain the hostname of the URL entered into the `href` attribute. Using `target="_blank"`, the URL on the anchor tag will maintain the hostname of the URL in the `href` attribute. Using `origin="dynamic"` in combination with `target="_blank"` will allow the targeted link to match the hostname of the current page.

<div class="exampleWrapper">
<!-- AURO-GENERATED-CONTENT:START (FILE:src=./../apiExamples/dynamichostname.html) -->
<!-- AURO-GENERATED-CONTENT:END -->
</div>

<auro-accordion alignRight>
<span slot="trigger">See code</span>

<!-- AURO-GENERATED-CONTENT:START (CODE:src=./../apiExamples/dynamichostname.html) -->
<!-- AURO-GENERATED-CONTENT:END -->

</auro-accordion>

## External Links

Hyperlinks when used with the `target="_blank"` attribute are domain aware and return either an internal domain new-window icon versus an icon that communicates users will be taken to a new domain.
Expand Down
17 changes: 17 additions & 0 deletions docs/partials/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,23 @@ If the `href` attribute is not added, the hyperlink element will render back sim

</auro-accordion>

## Dynamic hostname support

The `auro-hyperlink` component defaults to modifying the origin of any provided URL to match the hostname of the current page unless explicitly configured. Using `origin="hostname"` will retain the hostname of the URL entered into the `href` attribute. Using `target="_blank"`, the URL on the anchor tag will maintain the hostname of the URL in the `href` attribute. Using `origin="dynamic"` in combination with `target="_blank"` will allow the targeted link to match the hostname of the current page.

<div class="exampleWrapper">
<!-- AURO-GENERATED-CONTENT:START (FILE:src=./../apiExamples/dynamichostname.html) -->
<!-- AURO-GENERATED-CONTENT:END -->
</div>

<auro-accordion alignRight>
<span slot="trigger">See code</span>

<!-- AURO-GENERATED-CONTENT:START (CODE:src=./../apiExamples/dynamichostname.html) -->
<!-- AURO-GENERATED-CONTENT:END -->

</auro-accordion>

## Navigation pattern example

Auro's hyperlink element can be used in many creative ways in combination with other elements for easy-to-manage UI solutions. This example uses the `auro-hyperlink` with the `auro-icon` elements with a little CSS to create a pretty popular nav style UI.
Expand Down
1 change: 1 addition & 0 deletions src/auro-hyperlink.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import tokensCss from "./tokens-css.js";
* @attr {String} href - Defines the URL of the linked page.
* @attr {String} target - Defines where to open the linked document.
* @attr {String} type - Defines the type of hyperlink; accepts `nav` or `cta`.
* @attr {String} origin - Defines hostname for the origin of the URL. Options are `hostname` or `dynamic`. Default is `dynamic`. Using `hostname` will force the URL to be rewritten to use the hostname of the `href` attribute. Using `dynamic` will rewrite the URL to use the hostname of the current page.
* @csspart link - Allows styling to be applied to the `a` element.
* @csspart targetIcon - Allows styling to be applied to the icon that appears next to the hyperlink.
*/
Expand Down
77 changes: 65 additions & 12 deletions src/component-base.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export default class ComponentBase extends LitElement {
static get properties() {
return {
href: { type: String },
origin: { type: String },
rel: { type: String },
role: { type: String },
target: { type: String },
Expand Down Expand Up @@ -147,19 +148,67 @@ export default class ComponentBase extends LitElement {
* @returns {string|undefined} The safe URL or `undefined`.
*/
safeUrl(href, relative) {

if (!href) {
return undefined;
}

const url = new URL(href, 'https://www.alaskaair.com');
/**
* Determines the current hostname based on the environment (browser or server-side).
*
* @param {string} [href] - The URL to extract the hostname from when running in a server-side environment.
* @returns {string} The current hostname.
*/
let currentHostname;
if (typeof window !== 'undefined') {
currentHostname = window.location.origin;
} else {
// Extract the hostname from the provided href for SSR
try {
currentHostname = new URL(href).origin;
} catch (e) {
// If href is not a valid URL, provide a default fallback
currentHostname = 'http://localhost:8000';
}
}

/**
* Processes the given href and currentHostname to generate a URL object.
*
* @param {string} href - The href attribute of the anchor element.
* @param {string} currentHostname - The current hostname to be used as a base.
* @returns {URL|undefined} The generated URL object, or undefined if an error occurs.
* @private
*/
let url;


// let's refactor this so that it's using a enum versus a boolean value
try {
url = new URL(href, currentHostname);
if (this.origin !== `hostname` || this.origin === `dynamic`) {
const currentUrl = new URL(currentHostname);
url.hostname = currentUrl.hostname;
url.port = currentUrl.port; // Preserve the port number
}
if (this.target === `_blank` && this.origin === undefined) {
const hrefURL = new URL(href);
url.hostname = hrefURL.hostname;
url.port = ''; // Remove the port number
}
} catch (e) {
return undefined;
}

// return href if protocol is supported
switch (url.protocol) {
case 'tel:':
case 'sms:':
case 'mailto:':
return href;

// Specifically want to render NO shadowDOM for the following refs
// Return undefined for unsupported protocols
// renders NO shadow DOM in the UI
case 'javascript:':
case 'data:':
case 'vbscript:':
Expand Down Expand Up @@ -215,22 +264,26 @@ export default class ComponentBase extends LitElement {
* @returns {HTMLElement|undefined} The HTML element containing the icon, or undefined if no icon is generated.
*/
targetIcon(target) {

/**
* Checks if a URL's domain is from the 'alaskaair.com' domain or its subdomains.
* Checks if a given URL belongs to an internal domain.
*
* @param {string} url - The URL to check.
* @returns {boolean} Returns true if the URL's domain is 'alaskaair.com' or one of its subdomains, otherwise false.
* @returns {boolean} - Returns true if the URL is an internal domain, otherwise false.
*/
const isAlaskaAirDomain = (url) => {
const isInternalDomain = (url) => {
const urlObject = new URL(url);
return urlObject.hostname.endsWith('.alaskaair.com');
const hostname = urlObject.hostname;
const isInternal = hostname.includes('.alaskaair.com') ||
hostname.includes('.hawaiianairlines.com') ||
hostname.includes('localhost');
return isInternal;
};

// If target is '_blank' and the URL's domain is 'alaskaair.com' or one of its subdomains, return icon for new window
if (target === '_blank' && isAlaskaAirDomain(this.safeUri)) {
// If target is '_blank' and the URL's domain is internal or one of its subdomains,
// return icon for new window
if (target === '_blank' && isInternalDomain(this.safeUri)) {
return this.generateIconHtml(newWindow.svg);
} else if (target === '_blank' && !isAlaskaAirDomain(this.safeUri) && this.includesDomain) {
// If target is '_blank' and the URL does not belong to 'alaskaair.com' or its subdomains but contains a domain, return icon for external link
} else if (target === '_blank' && !isInternalDomain(this.safeUri) && this.includesDomain) {
// If target is '_blank' and the URL is not internal, return icon for external link
return this.generateIconHtml(externalLink.svg);
}

Expand Down
45 changes: 43 additions & 2 deletions test/auro-hyperlink.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ describe('auro-hyperlink', () => {

it('auro-hyperlink is accessible', async () => {
const el = await fixture(html`
<auro-hyperlink href="alaska.com">Alaska air</auro-hyperlink>
<auro-hyperlink href="www.foobar.com/faq.html">Alaska air</auro-hyperlink>
`);

// console.log(el.shadowRoot);
await expect(el).to.be.accessible();
});

Expand Down Expand Up @@ -37,6 +38,42 @@ describe('auro-hyperlink', () => {
expect(anchor).not.to.have.attribute('href', 'https://www.alaskaair.com/auro');
});

it('inserted URL is updated to be dynamic based on environment', async () => {
const el = await fixture(html`
<auro-hyperlink href="https://www.alaskaair.com/faq.html">It's Auro!</auro-hyperlink>
`);

const anchor = el.shadowRoot.querySelector('a');
expect(anchor).to.have.attribute('href').that.includes('localhost');
});

it('inserted URL is expecte to maintain initial URL hostname', async () => {
const el = await fixture(html`
<auro-hyperlink origin="hostname" href="https://www.alaskaair.com/faq.html">It's Auro!</auro-hyperlink>
`);

const anchor = el.shadowRoot.querySelector('a');
expect(anchor).to.have.attribute('href').that.includes('alaskaair');
});

it('shadow DOM href is persisted from original href with target=_blank', async () => {
const el = await fixture(html`
<auro-hyperlink target="_blank" href="https://www.alaskaair.com/faq.html">It's Auro!</auro-hyperlink>
`);

const anchor = el.shadowRoot.querySelector('a');
expect(anchor).to.have.attribute('href').that.includes('alaskaair');
});

it('shadow DOM anchor is dynamic even with target=_blank applied', async () => {
const el = await fixture(html`
<auro-hyperlink origin="dynamic" target="_blank" href="https://www.alaskaair.com/faq.html">It's Auro!</auro-hyperlink>
`);

const anchor = el.shadowRoot.querySelector('a');
expect(anchor).to.have.attribute('href').that.includes('localhost');
});

// eval that JS in the href attr is ignored
it('auro-hyperlink is javascript', async () => {
const el = await fixture(html`
Expand Down Expand Up @@ -148,11 +185,15 @@ describe('safeUrl function', () => {
});

it('returns href when protocol is https:', async () => {
const result = component.safeUrl('https://www.example.com', false);
component.origin = 'hostname'; // Set the origin value

const result = component.safeUrl('http://www.example.com', false);
expect(result).to.equal('https://www.example.com/');
});

it('returns href with https protocol when relative is false', async () => {
component.origin = 'hostname'; // Set the origin value

const result = component.safeUrl('http://www.example.com', false);
expect(result).to.equal('https://www.example.com/');
});
Expand Down

0 comments on commit 348358f

Please sign in to comment.