diff options
Diffstat (limited to 'app/assets/javascripts')
4 files changed, 94 insertions, 4 deletions
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js index 052e33b4a2b..d5d8edd5ac0 100644 --- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js @@ -1,26 +1,67 @@ import Mousetrap from 'mousetrap'; -import { getLocationHash, visitUrl } from '../../lib/utils/url_utility'; +import { + getLocationHash, + updateHistory, + urlIsDifferent, + urlContainsSha, + getShaFromUrl, +} from '~/lib/utils/url_utility'; +import { updateRefPortionOfTitle } from '~/repository/utils/title'; import Shortcuts from './shortcuts'; const defaults = { skipResetBindings: false, fileBlobPermalinkUrl: null, + fileBlobPermalinkUrlElement: null, }; +function eventHasModifierKeys(event) { + // We ignore alt because I don't think alt clicks normally do anything special? + return event.ctrlKey || event.metaKey || event.shiftKey; +} + export default class ShortcutsBlob extends Shortcuts { constructor(opts) { const options = Object.assign({}, defaults, opts); super(options.skipResetBindings); this.options = options; + this.shortcircuitPermalinkButton(); + Mousetrap.bind('y', this.moveToFilePermalink.bind(this)); } moveToFilePermalink() { - if (this.options.fileBlobPermalinkUrl) { + const permalink = this.options.fileBlobPermalinkUrl; + + if (permalink) { const hash = getLocationHash(); const hashUrlString = hash ? `#${hash}` : ''; - visitUrl(`${this.options.fileBlobPermalinkUrl}${hashUrlString}`); + + if (urlIsDifferent(permalink)) { + updateHistory({ + url: `${permalink}${hashUrlString}`, + title: document.title, + }); + } + + if (urlContainsSha({ url: permalink })) { + updateRefPortionOfTitle(getShaFromUrl({ url: permalink })); + } + } + } + + shortcircuitPermalinkButton() { + const button = this.options.fileBlobPermalinkUrlElement; + const handleButton = e => { + if (!eventHasModifierKeys(e)) { + e.preventDefault(); + this.moveToFilePermalink(); + } + }; + + if (button) { + button.addEventListener('click', handleButton); } } } diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index d48678c21f6..202363a1dda 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -1,6 +1,14 @@ const PATH_SEPARATOR = '/'; const PATH_SEPARATOR_LEADING_REGEX = new RegExp(`^${PATH_SEPARATOR}+`); const PATH_SEPARATOR_ENDING_REGEX = new RegExp(`${PATH_SEPARATOR}+$`); +const SHA_REGEX = /[\da-f]{40}/gi; + +// Reset the cursor in a Regex so that multiple uses before a recompile don't fail +function resetRegExp(regex) { + regex.lastIndex = 0; /* eslint-disable-line no-param-reassign */ + + return regex; +} // Returns a decoded url parameter value // - Treats '+' as '%20' @@ -128,6 +136,20 @@ export function doesHashExistInUrl(hashName) { return hash && hash.includes(hashName); } +export function urlContainsSha({ url = String(window.location) } = {}) { + return resetRegExp(SHA_REGEX).test(url); +} + +export function getShaFromUrl({ url = String(window.location) } = {}) { + let sha = null; + + if (urlContainsSha({ url })) { + [sha] = url.match(resetRegExp(SHA_REGEX)); + } + + return sha; +} + /** * Apply the fragment to the given url by returning a new url string that includes * the fragment. If the given url already contains a fragment, the original fragment @@ -154,6 +176,16 @@ export function visitUrl(url, external = false) { } } +export function updateHistory({ state = {}, title = '', url, replace = false, win = window } = {}) { + if (win.history) { + if (replace) { + win.history.replaceState(state, title, url); + } else { + win.history.pushState(state, title, url); + } + } +} + export function refreshCurrentPage() { visitUrl(window.location.href); } @@ -282,3 +314,7 @@ export const setUrlParams = (params, url = window.location.href, clearParams = f }; export const escapeFileUrl = fileUrl => encodeURIComponent(fileUrl).replace(/%2F/g, '/'); + +export function urlIsDifferent(url, compare = String(window.location)) { + return url !== compare; +} diff --git a/app/assets/javascripts/pages/projects/init_blob.js b/app/assets/javascripts/pages/projects/init_blob.js index bd8afa2d5ba..e862456f429 100644 --- a/app/assets/javascripts/pages/projects/init_blob.js +++ b/app/assets/javascripts/pages/projects/init_blob.js @@ -25,6 +25,7 @@ export default () => { new ShortcutsBlob({ skipResetBindings: true, fileBlobPermalinkUrl, + fileBlobPermalinkUrlElement, }); new BlobForkSuggestion({ diff --git a/app/assets/javascripts/repository/utils/title.js b/app/assets/javascripts/repository/utils/title.js index ff16fbdd420..9c4b334a1ce 100644 --- a/app/assets/javascripts/repository/utils/title.js +++ b/app/assets/javascripts/repository/utils/title.js @@ -1,5 +1,5 @@ const DEFAULT_TITLE = '· GitLab'; -// eslint-disable-next-line import/prefer-default-export + export const setTitle = (pathMatch, ref, project) => { if (!pathMatch) { document.title = `${project} ${DEFAULT_TITLE}`; @@ -12,3 +12,15 @@ export const setTitle = (pathMatch, ref, project) => { /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */ document.title = `${isEmpty ? 'Files' : path} · ${ref} · ${project} ${DEFAULT_TITLE}`; }; + +export function updateRefPortionOfTitle(sha, doc = document) { + const { title = '' } = doc; + const titleParts = title.split(' · '); + + if (titleParts.length > 1) { + titleParts[1] = sha; + + /* eslint-disable-next-line no-param-reassign */ + doc.title = titleParts.join(' · '); + } +} |