Skip to content

Commit

Permalink
Add thread status tooltips
Browse files Browse the repository at this point in the history
  • Loading branch information
SimonAlling committed Sep 26, 2019
1 parent 5952d43 commit 773d25d
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/globals-site.ts
Expand Up @@ -57,6 +57,8 @@ export const CLASS = <const> {
forumPostControls: "controls",
forumPostBtnGroup: "btnGroup",
forumPostByCurrentUser: "isReader", // Don't use isAuthor; it means submitted by OP!
threadStatus: "threadStatus",
threadStatusIcon: (bitmask: number) => `icon-${bitmask}`,
bbImage: "bbImage",
imgControls: "imgControls",
errorDialog: "errorDialog",
Expand Down
6 changes: 6 additions & 0 deletions src/preferences/general.ts
Expand Up @@ -42,6 +42,12 @@ export default <const> {
label: T.preferences.general.replace_followed_threads_link,
description: T.preferences.general.replace_followed_threads_link_description,
}),
thread_status_tooltips: new BooleanPreference({
key: "thread_status_tooltips",
default: true,
label: T.preferences.general.thread_status_tooltips,
description: T.preferences.general.thread_status_tooltips_description,
}),
insert_web_search_button: new BooleanPreference({
key: "insert_web_search_button",
default: true,
Expand Down
9 changes: 9 additions & 0 deletions src/styles.ts
Expand Up @@ -10,6 +10,7 @@ import { hideById, hideByClass, hideBySelector } from "./styles/hide";
import { timeIsWithin } from "./time"
import * as ms from "milliseconds";
import filterNewInForum from "./styles/interests-new-in-forum";
import threadStatusTooltips from "./styles/thread-status-tooltips-logic";

const ALWAYS: boolean = true;

Expand Down Expand Up @@ -116,6 +117,14 @@ const STYLESHEET_MODULES: ReadonlyArray<StylesheetModule> = [
condition: Preferences.get(P.advanced._.custom_css_enable),
css: Preferences.get(P.advanced._.custom_css_code),
},
{
condition: Preferences.get(P.general._.thread_status_tooltips),
css: require("styles/thread-status-tooltips"),
},
{
condition: Preferences.get(P.general._.thread_status_tooltips),
css: threadStatusTooltips(),
},

// Customize content:
{
Expand Down
58 changes: 58 additions & 0 deletions src/styles/thread-status-tooltips-logic.ts
@@ -0,0 +1,58 @@
import * as CONFIG from "globals-config";
import * as SITE from "globals-site";
import * as T from "text";
import { unlines } from "lines-unlines";

/*
SweClockers uses a system where icon class names (such as "icon-41") are essentially bitmasks.
For each item in this array, the number is the bitmask for that specific status.
For example, "icon-41" means locked, unread and following (because 41 = 1 + 8 + 32).
*/
const STATUSES = <const> [
// The order here decides the order in the tooltips.
[ 1, T.thread_status.locked ],
[ 2, T.thread_status.sticky ],
// 4 is nothing; probably meant "moved" in the past
[ 8, T.thread_status.unread ],
[ 64, T.thread_status.hot ],
[ 16, T.thread_status.participated ],
[ 32, T.thread_status.following ],
];

const DEFAULT_STATUS = T.thread_status.default; // when none of the other statuses match

type PowerOfTwo = 1 | 2 | 4 | 8 | 16 | 32 | 64 // 0 intentionally left out

/*
The generated CSS contains rules with selectors that never match, because some status combinations (e.g. locked + sticky) are not actually used.
However, this makes the code simpler, and it also means that we have ourselves covered should SweClockers introduce new combinations.
At the time of writing this, there were 28 combinations in use and we generated 64 rules.
*/
export default function(): string {
const powersUsedBySweClockers: ReadonlyArray<PowerOfTwo> = STATUSES.map(([ p, _ ]) => p);
const iconBitmasks = powersUsedBySweClockers.reduce(extend, [ 0 ]);
return unlines(iconBitmasks.map(styleRule));
}

function styleRule(bitmask: number): string {
return `.${SITE.CLASS.threadStatus}.${SITE.CLASS.threadStatusIcon(bitmask)}::after { content: '${tooltip(bitmask)}' }`;
}

function tooltip(bitmask: number): string {
return statuses(bitmask).join(T.thread_status.separator);
}

function statuses(bitmask: number): ReadonlyArray<string> {
const matchingStatuses = (
STATUSES
.filter(([ statusBitmask, _ ]) => statusBitmask & bitmask)
.map(([ _, description ]) => description)
);
return matchingStatuses.length > 0 ? matchingStatuses : [ DEFAULT_STATUS ];
}

// extend([ 0, 1 ] , 8) === [ 0, 1, 8, 9 ]
// extend([ 0, 10, 20 ], 4) === [ 0, 10, 20, 4, 14, 24 ]
function extend(xs: ReadonlyArray<number>, n: PowerOfTwo): ReadonlyArray<number> {
return xs.concat(xs.map(x => x + n));
}
25 changes: 25 additions & 0 deletions src/styles/thread-status-tooltips.scss
@@ -0,0 +1,25 @@
.#{getGlobal("SITE.CLASS.threadStatus")} {
position: relative; // to enable absolute positioning of tooltips

&::after {
background-color: black;
border-radius: 4px;
color: white;
display: inline-block; // necessary to negate link :hover underline
left: calc(100% + 10px);
min-width: fit-content;
opacity: 0;
padding: 3px 6px;
position: absolute;
top: 75%;
transition: opacity 100ms;
transition-delay: 0ms; // when hover ends
white-space: pre;
}

&:hover::after {
opacity: 0.9;
transition-delay: 300ms; // when hover starts
}

}
16 changes: 16 additions & 0 deletions src/text.ts
Expand Up @@ -77,6 +77,8 @@ export const preferences = <const> {
insert_preferences_shortcut_description: `Visa en länk till ${CONFIG.USERSCRIPT_NAME} inställningsmeny högst upp`,
replace_followed_threads_link: `Ersätt länken <em>Följda trådar</em> med <em>${my_posts}</em>`,
replace_followed_threads_link_description: OBVIOUS,
thread_status_tooltips: `Tooltips på trådstatusikoner`,
thread_status_tooltips_description: `Hovra med musen för att se vad de olika ikonerna betyder i forumets översiktsvyer`,
insert_web_search_button: `Webbsökknapp`,
insert_web_search_button_description: `När ${SITE.NAME} inbyggda sökfunktion inte hittar det du söker`,
search_engine: {
Expand Down Expand Up @@ -278,3 +280,17 @@ export const special_characters: ReadonlyArray<InsertButtonDescription> = [
{ insert: "✓", tooltip: "Bock" },
{ insert: "→", tooltip: "Högerpil" },
];

export const thread_status = <const> {
// https://www.sweclockers.com/forum/trad/1367915-faq-tradikoner-och-deras-betydelser
// NOTE! Apostrophes must be (double-)escaped because these strings are used as CSS `content` values. For example:
// editors_choice: `Editor\\'s choice`,
unread: `Olästa inlägg`, // Öppet kuvert
default: `Gå till tråden`, // Stängt kuvert
hot: `50+ inlägg`, // Orange kuvert
participated: `Du har deltagit`, // Sigill (punkt)
following: `Du följer tråden`, // Stjärna
locked: `Låst`, // Hänglås
sticky: `Klistrad`, // Knappnål
separator: " • ",
};

0 comments on commit 773d25d

Please sign in to comment.