mirror of
https://github.com/jareika/Simple-Timeline.git
synced 2026-07-22 07:01:40 +00:00
New timeline design
Horizontal timeline added with bases support.
This commit is contained in:
parent
b16444a8dd
commit
f2bef14f20
7 changed files with 2419 additions and 1100 deletions
180
README.md
180
README.md
|
|
@ -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 callout‑like 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: fixed‑height card using your theme’s 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 (multi‑line 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 multi‑line ellipsis.
|
||||
- Colors and sizing:
|
||||
- Global defaults for image size, card height, paddings and colors.
|
||||
- Per‑timeline 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 vault’s `.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
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-simple-timeline",
|
||||
"version": "0.3.3",
|
||||
"version": "0.4.0",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
|
|
|||
1887
src/main.ts
1887
src/main.ts
File diff suppressed because it is too large
Load diff
145
styles.css
145
styles.css
|
|
@ -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 multi‑line 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;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
Loading…
Reference in a new issue