forked from wagtail/wagtail
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix content path links in usage view to scroll to the correct element
- Loading branch information
1 parent
8661056
commit 3b22cbf
Showing
9 changed files
with
210 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import { | ||
getWagtailDirectives, | ||
getContentPathSelector, | ||
getElementByContentPath, | ||
} from './contentPath'; | ||
|
||
describe('getWagtailDirectives', () => { | ||
afterEach(() => { | ||
window.location.hash = ''; | ||
}); | ||
|
||
it('should return the directive after the delimiter as-is', () => { | ||
window.location.hash = '#:w:contentpath=abc1.d2e.3f'; | ||
expect(getWagtailDirectives()).toEqual('contentpath=abc1.d2e.3f'); | ||
}); | ||
|
||
it('should allow a normal anchor in front of the delimiter', () => { | ||
window.location.hash = '#an-anchor:w:contentpath=abc1.d2e.3f'; | ||
expect(getWagtailDirectives()).toEqual('contentpath=abc1.d2e.3f'); | ||
}); | ||
|
||
it('should allow multiple values for the same directive', () => { | ||
window.location.hash = | ||
'#hello:w:contentpath=abc1.d2e.3f&unknown=123&unknown=456'; | ||
expect(getWagtailDirectives()).toEqual( | ||
'contentpath=abc1.d2e.3f&unknown=123&unknown=456', | ||
); | ||
}); | ||
}); | ||
|
||
describe('getContentPathSelector', () => { | ||
it('should return a selector string for a single content path', () => { | ||
expect(getContentPathSelector('abc1')).toEqual('[data-contentpath="abc1"]'); | ||
}); | ||
it('should allow dotted content path', () => { | ||
expect(getContentPathSelector('abc1.d2e.3f')).toEqual( | ||
'[data-contentpath="abc1"] [data-contentpath="d2e"] [data-contentpath="3f"]', | ||
); | ||
}); | ||
|
||
it('should ignore leading, trailing, and extra dots', () => { | ||
expect(getContentPathSelector('.abc1...d2e..3f.')).toEqual( | ||
'[data-contentpath="abc1"] [data-contentpath="d2e"] [data-contentpath="3f"]', | ||
); | ||
}); | ||
|
||
it('should return an empty string if content path is an empty string', () => { | ||
expect(getContentPathSelector('')).toEqual(''); | ||
}); | ||
}); | ||
|
||
describe('getElementByContentPath', () => { | ||
beforeEach(() => { | ||
document.body.innerHTML = /* html */ ` | ||
<div id="one" data-contentpath="abc1"> | ||
<div id="two" data-contentpath="d2e"> | ||
<div id="three" data-contentpath="3f"></div> | ||
</div> | ||
<div id="four" data-contentpath="g4h"></div> | ||
</div> | ||
`; | ||
}); | ||
|
||
afterEach(() => { | ||
window.location.hash = ''; | ||
}); | ||
|
||
it('should return the element for a single content path', () => { | ||
const element = getElementByContentPath('abc1'); | ||
expect(element).toBeTruthy(); | ||
expect(element.id).toEqual('one'); | ||
}); | ||
|
||
it('should return the element for a dotted content path', () => { | ||
const element = getElementByContentPath('abc1.d2e.3f'); | ||
expect(element).toBeTruthy(); | ||
expect(element.id).toEqual('three'); | ||
}); | ||
|
||
it('should read from the contentpath directive if there is one', () => { | ||
window.location.hash = '#:w:contentpath=abc1.d2e.3f'; | ||
const element = getElementByContentPath(); | ||
expect(element).toBeTruthy(); | ||
expect(element.id).toEqual('three'); | ||
}); | ||
|
||
it('should return null if it cannot find the element', () => { | ||
expect(getElementByContentPath('abc1.d2e.3f.g4h')).toBeNull(); | ||
expect(getElementByContentPath()).toBeNull(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
const WAGTAIL_DIRECTIVE_DELIMITER = ':w:'; | ||
|
||
/** | ||
* Extract the Wagtail directives from the URL fragment. | ||
* | ||
* This follows the algorithm described in | ||
* https://wicg.github.io/scroll-to-text-fragment/#extracting-the-fragment-directive | ||
* for extracting the fragment directive from the URL fragment, with a few | ||
* differences: | ||
* - We use a :w: delimiter instead of the proposed :~: delimiter. | ||
* - We don't remove our directive from the URL fragment. | ||
* | ||
* @param rawFragment The raw fragment (hash) from the URL, | ||
* @returns a string of Wagtail directives, if any, in the style of URL search parameters. | ||
* | ||
* @example window.location.hash = '#:w:contentpath=abc1.d2e.3f' | ||
* // getWagtailDirectives() === 'contentpath=abc1.d2e.3f' | ||
* | ||
* @example window.location.hash = '#an-anchor:w:contentpath=abc1.d2e.3f' | ||
* // getWagtailDirectives() === 'contentpath=abc1.d2e.3f' | ||
* | ||
* @example window.location.hash = '#hello:w:contentpath=abc1.d2e.3f&unknown=123&unknown=456' | ||
* // getWagtailDirectives() === 'contentpath=abc1.d2e.3f&unknown=123&unknown=456' | ||
*/ | ||
export function getWagtailDirectives() { | ||
const rawFragment = window.location.hash; | ||
const position = rawFragment.indexOf(WAGTAIL_DIRECTIVE_DELIMITER); | ||
if (position === -1) return ''; | ||
return rawFragment.slice(position + WAGTAIL_DIRECTIVE_DELIMITER.length); | ||
} | ||
|
||
/** | ||
* Compose a selector string to find the content element based on the dotted | ||
* content path. | ||
* | ||
* @param contentPath dotted path to the content element. | ||
* @returns a selector string to find the content element. | ||
* | ||
* @example getContentPathSelector('abc1.d2e.3f') | ||
* // returns '[data-contentpath="abc1"] [data-contentpath="d2e"] [data-contentpath="3f"]' | ||
*/ | ||
export function getContentPathSelector(contentPath: string) { | ||
const pathSegments = contentPath.split('.'); | ||
const selector = pathSegments.reduce((acc, segment) => { | ||
// In some cases the segment can be empty, e.g. when the path ends with | ||
// a trailing dot, which may be the case with inline panels. | ||
if (!segment) return acc; | ||
|
||
const segmentSelector = `[data-contentpath="${segment}"]`; | ||
return acc ? `${acc} ${segmentSelector}` : segmentSelector; | ||
}, ''); | ||
return selector; | ||
} | ||
|
||
/** | ||
* Get the content element based on a given content path (or one extracted from | ||
* the URL hash fragment). | ||
* | ||
* @param contentPath (optional) content path to the content element. If not | ||
* provided, it will be extracted from the URL fragment. | ||
* @returns the content element, if found, otherwise `null`. | ||
* | ||
* @example getElementByContentPath('abc1.d2e.3f') | ||
* // returns <div data-contentpath="3f">...</div> | ||
* | ||
* @example getElementByContentPath() | ||
* // with an URL e.g. https://example.com/#:w:contentpath=abc1.d2e.3f | ||
* // returns <div data-contentpath="3f">...</div> | ||
*/ | ||
export function getElementByContentPath(contentPath?: string) { | ||
const path = | ||
contentPath || | ||
new URLSearchParams(getWagtailDirectives()).get('contentpath'); | ||
|
||
return path | ||
? document.querySelector<HTMLElement>(getContentPathSelector(path)) | ||
: null; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters