New timeline design

Horizontal timeline added with bases support.
This commit is contained in:
Jareika 2026-01-02 18:11:41 +01:00
parent b16444a8dd
commit f2bef14f20
7 changed files with 2419 additions and 1100 deletions

180
README.md
View file

@ -1,66 +1,128 @@
# TTRPG Tools: Timeline
# Simple Timeline (Obsidian Plugin)
Simple Timeline is an Obsidian plugin that renders visual timelines from note frontmatter.
Simple Timeline renders timelines from note frontmatter. It can be used in:
It reads `fc-date` / `fc-end` fields (compatible with [Calendarium]-style frontmatter) and shows each note as a card: image on the left, a calloutlike box on the right, with native Obsidian popovers on hover.
- Markdown code blocks (`timeline-cal`, `timeline-h`)
- Bases views (optional integration)
## Features
It is designed to be theme-friendly and configurable.
- Uses `fc-date` / `fc-end` from note frontmatter.
- “Cross” layout:
- Left: fixed image with rounded corners.
- Right: fixedheight card using your themes H1/H4 styles.
- Native Obsidian page preview:
- Hover over image or card to open the note preview.
- Works without holding Ctrl.
- Responsive layout:
- Wide screens: image left, card right.
- Narrow screens: image stacked on top of the card, behaving like a single combined “card”.
- Summary handling:
- Optional `tl-summary` frontmatter (multiline YAML `|` / `|-` supported).
- If missing, the plugin uses the first meaningful paragraph from the note body.
- Summary is clamped to a configurable number of lines with a native multiline ellipsis.
- Colors and sizing:
- Global defaults for image size, card height, paddings and colors.
- Pertimeline overrides (including title/date color and hover background).
- Month names:
- English by default (`January`…`December`).
- Custom month names per timeline (useful for fantasy calendars).
- No runtime dependency on Calendarium:
- Just reuses its `fc-date` / `fc-end` convention.
## Installation
Until it is available in the community plugin browser, you can install it manually:
1. Build the plugin (or download a release ZIP, if available).
2. Copy the contents (`main.js`, `manifest.json`, `styles.css` and `versions.json`) into a folder named `simple-timeline` inside your vaults `.obsidian/plugins` directory.
3. Enable **Simple Timeline** in *Settings → Community plugins*.
4. Custom month names: In your timeline settings you can write down your own fantasy or whatever month names, just separate them with commas (,).
Minimum Obsidian version: `1.6.7`.
## Usage
### 1. Frontmatter in your notes
Add at least `fc-date` to any note you want to see on a timeline:
```yaml
---
fc-date: 1165-04-01 # required
fc-end: 1165-04-03 # optional (end of range)
timelines: [Travel] # one or more timeline names
tl-title: Arrival in Hollowhome 2 # optional display title
tl-summary: |- # optional custom summary
Late in the afternoon we reached the old cemetery...
The fence was swallowed by vines...
tl-image: assets/hollowhome.jpg # optional image override
---
```
### 2. YAML Codeblock to display the timeline
```yaml
## Install / Enable
1. Install the plugin (manual or via BRAT / community store if available).
2. Enable it in **Settings → Community plugins**.
3. Enable **Page Preview** (recommended) to use hover popovers.
---
## Required Frontmatter
A note becomes a timeline entry when it has `fc-date`.
### Minimal
fc-date: 1165-03-01
fc-end: 1165-03-03
timelines: [Travelbook 1]
tl-title: Arrival in New York
tl-summary: |-
We arrived after two days of travel.
Weather: rain and wind.
tl-image: assets/my-image.png
Image can be:
- an external URL (https://...)
- an internal link/path to a vault file
Image selection priority
The plugin tries to find an image in this order:
- tl-image (frontmatter)
- first internal embed image (![[...]])
- first markdown image link (![](…))
- first image file in the note folder
## Markdown Code Blocks
A) Vertical “Cross” layout (default renderer)
~~~
```timeline-cal
name/names: YourTimeline, YourSecondTimelineIfYouLikeToShowTwo...orMore
```
names: Travelbook 1, Travelbook 2
jumpToToday: true
```
~~~
Shows one entry per row (image + callout box).
B) Horizontal timeline
~~~
```timeline-h
names: Reisebuch 1, Reisebuch 2
mode: stacked # stacked | mixed
jumpToToday: false
```
~~~
Modes:
- mixed: all entries from all timelines in one horizontal row (chronological)
- stacked: one horizontal row per timeline; aligned by the union of existing dates (gaps appear only when a timeline has no entry for a date that another timeline has)
## Settings (Global + Per Timeline)
You can configure:
- image width / height
- box height
- inner left/right padding
- max summary lines (multi-line ellipsis)
- colors (background, border, hover, title, date)
- per-timeline month names (manual) (name, name,...)
Per-timeline configuration is stored under a timeline key (the same name you use in timelines:).
## Calendarium “Today” (optional)
If the Calendarium plugin is installed, the Timeline UI can jump to “today”:
In Markdown blocks: jumpToToday: true
In Bases views: option jumpToToday: "true"
If Calendarium is not installed, the button will show a notice.
## Bases Integration (optional)
Enable it in plugin settings:
Settings → Simple Timeline → Bases integration
After enabling, restart Obsidian or reload the plugin (required for view registration).
A) Bases view: Timeline (Cross)
View type:
simple-timeline-cross
B) Bases view: Timeline (Horizontal)
View type:
simple-timeline-horizontal
Common options (property mapping):
- timelineConfig (optional, forces one style config for all cards)
- timelineProperty (used when timelineConfig is empty)
- startProperty, endProperty
- titleProperty, summaryProperty, imageProperty
- orderMode: bases | start-asc | start-desc
- jumpToToday: "true" | "false"
Horizontal-only option:
- mode: stacked | mixed
Example YAML for Bases:
~~~
```base
views:
- type: simple-timeline-horizontal # simple-timeline-cross
name: Timeline (mixed)
timelineConfig: ""
startProperty: note.fc-date
endProperty: note.fc-end
titleProperty: note.tl-title
summaryProperty: note.tl-summary
imageProperty: note.tl-image
orderMode: start-asc
```
~~~

1300
main.js

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
{
"id": "simple-timeline",
"name": "TTRPG Tools: Timeline",
"version": "0.3.3",
"version": "0.4.0",
"minAppVersion": "1.6.7",
"description": "Timeline-Renderer für Calendarium-Frontmatter (fc-date/fc-end). 200×315 Bild, Callout-Stil, native Popover, mobilfreundlich.",
"author": "Jareika",

View file

@ -1,6 +1,6 @@
{
"name": "obsidian-simple-timeline",
"version": "0.3.3",
"version": "0.4.0",
"license": "MIT",
"private": true,
"scripts": {

File diff suppressed because it is too large Load diff

View file

@ -94,15 +94,15 @@
.tl-box .tl-title.tl-title-colored,
.tl-box .tl-date.tl-date-colored {
position: relative;
display: inline-block; /* isoliert den Unterstrich auf Textbreite */
border: 0 !important; /* Theme-Borders aus */
display: inline-block;
border: 0 !important;
border-bottom: 0 !important;
box-shadow: none !important; /* Theme-Schatten aus */
background: none !important; /* Theme-Hintergründe aus */
box-shadow: none !important;
background: none !important;
background-image: none !important;
}
/* H1 line: slightly thicker, full width, close to the text */
/* H1 line */
.tl-box .tl-title.tl-title-colored::after {
content: "";
pointer-events: none;
@ -120,7 +120,7 @@
opacity: 0.95;
}
/* H4 / date line: shorter and visually distinct*/
/* H4 / date line */
.tl-box .tl-date.tl-date-colored::after {
content: "";
pointer-events: none;
@ -138,7 +138,7 @@
opacity: 0.95;
}
/* Summary: native multiline ellipsis; line count is set from JS */
/* Summary */
.tl-box .tl-summary {
position: relative;
flex: 1 1 auto;
@ -152,13 +152,12 @@
word-break: break-word;
}
/* Extra safety against overflow (no visual gradient, just clipping) */
.tl-box .tl-summary.tl-clamp {
max-height: calc(var(--tl-summary-lines, 7) * var(--tl-summary-lh, 1.4) * 1em);
overflow: hidden;
}
/* Hover: background changes, border keeps the accent color */
/* Hover */
.tl-media:hover + .tl-box.callout.has-media,
.tl-box.callout:hover {
background: var(
@ -180,53 +179,145 @@
cursor: pointer;
}
/* =========================================
Horizontal timeline (timeline-h)
========================================= */
.tl-h-scroller {
--tl-h-col-w: 600px;
--tl-h-gap: 0px; /* cards touch */
--tl-h-stack-gap: 26px;
overflow-x: auto;
overflow-y: visible;
-webkit-overflow-scrolling: touch;
padding-bottom: 8px;
}
.tl-h-content.tl-horizontal.tl-h-mixed {
display: flex;
flex-direction: row;
align-items: stretch;
gap: var(--tl-h-gap, 0px);
}
.tl-h-content.tl-horizontal.tl-h-stacked {
display: flex;
flex-direction: column;
gap: var(--tl-h-stack-gap, 26px);
}
.tl-h-item {
flex: 0 0 auto;
width: var(--tl-h-col-w, 600px);
padding-left: 0 !important;
padding-right: 0 !important;
}
.tl-horizontal .tl-row {
width: auto !important;
}
/* Ensure fixed card width */
.tl-h-item .tl-grid.no-media {
grid-template-columns: var(--tl-h-col-w, 600px);
}
.tl-h-item .tl-grid.has-media {
grid-template-columns:
var(--tl-media-w, 200px)
max(160px, calc(var(--tl-h-col-w, 600px) - var(--tl-media-w, 200px)));
}
.tl-h-item.tl-align-right .tl-grid.has-media {
grid-template-columns:
max(160px, calc(var(--tl-h-col-w, 600px) - var(--tl-media-w, 200px)))
var(--tl-media-w, 200px);
}
/* Join styling (ONLY when JS adds join-classes) */
.tl-h-item.tl-h-join-left-box .tl-box.callout {
border-left: none;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.tl-h-item.tl-h-join-right-box .tl-box.callout {
border-right: none;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
/* stacked grid per timeline row */
.tl-h-timeline {
display: block;
}
.tl-h-row {
display: grid;
grid-template-columns: repeat(var(--tl-h-cols, 1), var(--tl-h-col-w, 600px));
column-gap: var(--tl-h-gap, 0px);
row-gap: 12px;
align-items: start;
}
.tl-h-slot {
grid-column-start: var(--tl-h-col);
display: flex;
flex-direction: column;
gap: 12px;
}
/* =========================================
Responsive behaviour (tablet / mobile)
========================================= */
@media (max-width: 900px) {
.tl-horizontal {
--tl-h-col-w: 420px;
--tl-h-gap: 0px;
}
.tl-wrapper.tl-cross-mode {
gap: 16px;
}
.tl-row {
.tl-wrapper.tl-cross-mode .tl-row {
padding-left: 16px !important;
padding-right: 16px !important;
display: flex;
justify-content: center;
}
.tl-grid {
grid-template-columns: 1fr !important; /* image above card */
.tl-wrapper.tl-cross-mode .tl-grid {
grid-template-columns: 1fr !important;
justify-items: stretch;
width: 100%;
max-width: 560px;
row-gap: 0;
}
/* IMPORTANT: override desktop grid-areas for single-column layout */
.tl-grid.has-media {
.tl-wrapper.tl-cross-mode .tl-grid.has-media {
grid-template-areas:
"media"
"box" !important;
}
.tl-grid.no-media {
.tl-wrapper.tl-cross-mode .tl-grid.no-media {
grid-template-areas: "box" !important;
}
/* Image on top: visually part of the same “card”. */
.tl-media {
.tl-wrapper.tl-cross-mode .tl-media {
width: 100% !important;
max-width: 560px;
height: auto !important;
border-radius: 10px 10px 0 0;
border: none;
margin-bottom: -6px; /* lets the image slightly overlap the box */
margin-bottom: -6px;
background: transparent;
}
.tl-media img {
.tl-wrapper.tl-cross-mode .tl-media img {
width: 100%;
height: auto !important;
object-fit: cover;
@ -234,31 +325,27 @@
border-radius: 10px 10px 0 0;
}
/* Box below the image: together they form a single card */
.tl-box.callout {
.tl-wrapper.tl-cross-mode .tl-box.callout {
width: 100%;
max-width: 560px;
}
/* Mobile: stacked -> box always below image. Alignment doesn't matter here. */
.tl-box.callout.has-media {
.tl-wrapper.tl-cross-mode .tl-box.callout.has-media {
height: auto !important;
border-radius: 0 0 10px 10px;
border: 1px solid var(--tl-accent, var(--background-modifier-border));
border-top: none; /* no double border directly under the image */
border-top: none;
margin-top: 0;
}
/* Override desktop cut-edges for both alignments */
.tl-row.tl-align-right .tl-box.callout.has-media,
.tl-row:not(.tl-align-right) .tl-box.callout.has-media {
.tl-wrapper.tl-cross-mode .tl-row.tl-align-right .tl-box.callout.has-media,
.tl-wrapper.tl-cross-mode .tl-row:not(.tl-align-right) .tl-box.callout.has-media {
border: 1px solid var(--tl-accent, var(--background-modifier-border));
border-top: none;
border-radius: 0 0 10px 10px;
}
/* Without image: card stands on its own, full width, auto height */
.tl-box.callout.no-media {
.tl-wrapper.tl-cross-mode .tl-box.callout.no-media {
width: 100%;
max-width: 560px;
height: auto !important;

View file

@ -2,5 +2,6 @@
"0.2.9": "1.6.7",
"0.3.1": "1.6.7",
"0.3.2": "1.6.7",
"0.3.3": "1.6.7"
"0.3.3": "1.6.7",
"0.4.0": "1.6.7"
}