Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save lucaswilric/75f9492fc64befed433e32b3013bc1ab to your computer and use it in GitHub Desktop.
Save lucaswilric/75f9492fc64befed433e32b3013bc1ab to your computer and use it in GitHub Desktop.
This script adds a new sticky header to Basecamp projects and todo lists on scroll.
// ==UserScript==
// @name Basecamp - Projects Sticky Header
// @namespace http://tampermonkey.net/
// @version 0.13
// @description This script adds a new sticky header to Basecamp projects and todo lists on scroll. Never forget where you are again!
// @updateURL https://gist.github.com/lucaswilric/75f9492fc64befed433e32b3013bc1ab/raw/basecamp-projects-sticky-header.user.js
// @downloadURL https://gist.github.com/lucaswilric/75f9492fc64befed433e32b3013bc1ab/raw/basecamp-projects-sticky-header.user.js
// @author Dan Berkowitz, Lucas Wilson-Richter
// @match https://3.basecamp.com/*
// @grant GM_addStyle
// @require https://npmcdn.com/headroom.js/dist/headroom.min.js
// ==/UserScript==
GM_addStyle(
'#project-header-clone { pointer-events: none; transition: transform 120ms ease-in-out; font-size:0.9em; position:fixed; top:0; left:0; right:0; transform:translateY(19%); z-index:-1;}'
);
GM_addStyle(
'#project-header-clone:not(.headroom--top).sticky-pinned {transform:translateY(120%);}'
);
GM_addStyle(
'#project-header-clone.project-header-mobile__padding-top-bottom, #project-header-clone.project-guide__header { display:none; }'
);
GM_addStyle (
'.sticky-pinned h1 > span.options-menu { background-color:var(--color-bg--main); padding:.5em; box-shadow: 0 1px 3px 0 rgba(0,0,0,0.4); border-radius: 0 0 0.6rem 0.6rem;}'
);
GM_addStyle (
'#project-header-clone button.recording-breadcrumb__jump, #project-header-clone span.recording-breadcrumb__jump-menu {display:none;}'
);
GM_addStyle (
'.nav__main { background-color:var(--color-bg--main); max-width:100%; width:100%;'
);
GM_addStyle (
'#project-header-clone .crumb-wrapper { pointer-events: all; display:inline-block; background-color:var(--color-bg--main); padding:.5em; box-shadow: 0 1px 3px 0 rgba(0,0,0,0.4); border-radius: 0 0 0.6rem 0.6rem;}'
);
function scrollToTop(scrollDuration) {
var cosParameter = window.scrollY / 2,
scrollCount = 0,
oldTimestamp = performance.now();
function step (newTimestamp) {
scrollCount += Math.PI / (scrollDuration / (newTimestamp - oldTimestamp));
if (scrollCount >= Math.PI) window.scrollTo(0, 0);
if (window.scrollY === 0) return;
window.scrollTo(0, Math.round(cosParameter + cosParameter * Math.cos(scrollCount)));
oldTimestamp = newTimestamp;
window.requestAnimationFrame(step);
}
window.requestAnimationFrame(step);
}
function cloneHeader() {
var projectHeader = document.querySelector("div.recording-breadcrumb__title");
var mainNav = document.querySelector("ul.nav__main");
var clone = projectHeader.cloneNode(true); //Clone the projectHeader DOM node and all child elements
clone.id = "project-header-clone"; //Add an ID tag to the cloned node so I can target it individually.
mainNav.appendChild(clone); //Append the cloned node as a child of mainNav
// The breadcrumbs don't have a wrapper, so we need to add one to style them. We can also enhance the breadcrumbs by getting the task name from the window title and appending it
var modified_HTML = "<div class='crumb-wrapper'><a class='scrollTop' style='cursor:pointer;'>⬆</>" + clone.innerHTML + '<div class="recording-breadcrumb__separator" style="display:inline;" aria-hidden="true"> › </div>' + document.title + "</div>"; //Copy the breadcrumb nodes from inside the cloned element, get the page title (which is the task name), and then wrap them in a div
clone.innerHTML = modified_HTML; //Replace the cloned element's inner HTML with the newly wrapped nodes
// Add ability to scroll to top of page.
let scrollTopButton = document.querySelector('.scrollTop');
scrollTopButton.addEventListener('click', function(){
scrollToTop(200);
});
}
function makeSticky(x) {
var headroomJS = new Headroom(x, {
"offset" : 200,
"tolerance" : 0,
"classes" : {
"initial" : "sticky-top",
"pinned" : "sticky-pinned",
}
});
headroomJS.init(); //Initialize Headroom.JS
}
document.addEventListener("turbolinks:load", function() { // Hooks into Turbolinks' page load event (https://github.com/turbolinks/turbolinks#full-list-of-events)
var cloneExists = document.querySelector("#project-header-clone");
if ( cloneExists === null ) { // If element does not exist
cloneHeader(); // Clone and append the element
cloneExists = document.querySelector("#project-header-clone"); // After clone and append, redefine cloneExists variable in the local scope
}
else {
cloneExists.remove(); // Remove the cloned header due to it persisting for one page too long
cloneHeader(); // Clone and append the element
cloneExists = document.querySelector("#project-header-clone"); //Target the newly cloned and appended element
}
makeSticky(cloneExists);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment