fend updates
This commit is contained in:
112
CLAUDE.md
112
CLAUDE.md
@@ -7,9 +7,6 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
Deskmeter is a productivity tool that measures time spent across desktop workspaces. It consists of four main components:
|
Deskmeter is a productivity tool that measures time spent across desktop workspaces. It consists of four main components:
|
||||||
|
|
||||||
- **dmcore**: Core tracking daemon (`dmapp/dmcore/main.py`) that monitors active workspace and task changes
|
- **dmcore**: Core tracking daemon (`dmapp/dmcore/main.py`) that monitors active workspace and task changes
|
||||||
- **dmweb**: Flask web application (`dmapp/dmweb/`) for viewing productivity data
|
|
||||||
- **dmfnt**: Angular frontend (`dmapp/dmfnt/`) for enhanced UI (in development)
|
|
||||||
- **gnome-extension**: GNOME Shell extension (`gnome-extension/deskmeter-indicator@local/`) that displays current task in the top panel
|
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
@@ -32,21 +29,21 @@ Deskmeter is a productivity tool that measures time spent across desktop workspa
|
|||||||
### Web Interface Structure
|
### Web Interface Structure
|
||||||
|
|
||||||
**Flask Routes** (`dmapp/dmweb/dm.py`):
|
**Flask Routes** (`dmapp/dmweb/dm.py`):
|
||||||
- `/` - Today's productivity summary
|
- `/` - Today's productivity summary (large display with auto-refresh)
|
||||||
- `/day/<month>/<day>` - Single day view
|
- `/calendar/<scope>/<year>/<month>/<day>?grid=<1|3|6>` - Google Calendar-style view with aggregated task blocks
|
||||||
- `/calendar` - Google Calendar-style task timeline view (daily/weekly/monthly)
|
- Scopes: `daily`, `weekly`, `monthly`
|
||||||
- `/switches` - Raw switch documents view (daily/weekly/monthly)
|
- Grid: Hour aggregation (1h, 3h, or 6h blocks)
|
||||||
- `/workmonth` - Monthly calendar showing task totals via `dmapp/dmweb/dmcal.py`
|
- Shows only active workspaces (Plan/Think/Work) with gaps for idle time
|
||||||
|
- Cascading overlapping blocks for multiple tasks per time period
|
||||||
|
- `/switches/<scope>/<year>/<month>/<day>` - Raw switches view with proportional cell heights
|
||||||
|
- Shows all workspace switches with time-based height visualization
|
||||||
|
- `/work` - Work projects breakdown for today
|
||||||
- `/totals` - All-time statistics
|
- `/totals` - All-time statistics
|
||||||
- `/api/current_task` - JSON API endpoint returning current task info (for GNOME extension)
|
- `/day/<month>/<day>` - Single day view
|
||||||
|
- `/period/<start>/<end>` - Custom date range
|
||||||
|
- `/api/current_task` - JSON endpoint for current task info (used by GNOME extension)
|
||||||
- `/api/today` - HTML fragment for AJAX updates
|
- `/api/today` - HTML fragment for AJAX updates
|
||||||
|
|
||||||
**Task Info API** (`dmapp/dmweb/get_period_times.py`):
|
|
||||||
- `get_current_task_info()` - Retrieves current task ID and path from MongoDB state collection
|
|
||||||
- `get_task_path()` - Resolves task ID to path, with automatic task_history fallback
|
|
||||||
- `get_task_blocks_calendar()` - Groups consecutive switches by task, tracks active/idle time
|
|
||||||
- `get_raw_switches()` - Returns all switch documents for a period
|
|
||||||
|
|
||||||
## Development Commands
|
## Development Commands
|
||||||
|
|
||||||
### Python Backend
|
### Python Backend
|
||||||
@@ -71,24 +68,26 @@ cd dmapp/tests
|
|||||||
python3 test_dmapp.py
|
python3 test_dmapp.py
|
||||||
```
|
```
|
||||||
|
|
||||||
### Angular Frontend
|
### Running the Web Interface
|
||||||
|
|
||||||
|
Use the `dmweb.sh` script for flexible deployment:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd dmapp/dmfnt
|
cd /home/mariano/wdir/run
|
||||||
|
|
||||||
# Install dependencies
|
# Syntax: ./dmweb.sh <worktree> <port> <debug>
|
||||||
npm install
|
./dmweb.sh dm-fend-updates 10002 1
|
||||||
|
|
||||||
# Development server
|
# Parameters:
|
||||||
npm run start
|
# - worktree: directory name in /home/mariano/wdir/ (default: dm)
|
||||||
|
# - port: Flask server port (default: 10000)
|
||||||
# Build for production
|
# - debug: 1 for debug mode with auto-reload, 0 for production (default: 0)
|
||||||
npm run build
|
|
||||||
|
|
||||||
# Run tests
|
|
||||||
npm run test
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Angular Frontend (Not Yet Implemented)
|
||||||
|
|
||||||
|
The Angular frontend (`dmapp/dmfnt/`) mentioned in the architecture is planned but not currently implemented. All current functionality is in the Flask templates.
|
||||||
|
|
||||||
### GNOME Extension
|
### GNOME Extension
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -243,7 +242,7 @@ Both support daily, weekly, and monthly scopes with navigation.
|
|||||||
|
|
||||||
- The system enforces task constraints within work desktops - switching to a non-work task automatically reverts to the designated work task
|
- The system enforces task constraints within work desktops - switching to a non-work task automatically reverts to the designated work task
|
||||||
- Task files are monitored for changes to enable automatic context switching
|
- Task files are monitored for changes to enable automatic context switching
|
||||||
- Web interface runs on port 10000 by default
|
- Web interface runs on port 10000 by default (configurable via `dmweb.sh` script)
|
||||||
- Core daemon sleeps for 2 seconds between workspace checks
|
- Core daemon sleeps for 2 seconds between workspace checks
|
||||||
- GNOME extension debounce delay (2.2s) must be > dmcore polling interval (2s) to ensure MongoDB is updated before API query
|
- GNOME extension debounce delay (2.2s) must be > dmcore polling interval (2s) to ensure MongoDB is updated before API query
|
||||||
|
|
||||||
@@ -285,8 +284,63 @@ Both support daily, weekly, and monthly scopes with navigation.
|
|||||||
4. **Gradient indicates work quality** - Visual distinction between active focus and idle time
|
4. **Gradient indicates work quality** - Visual distinction between active focus and idle time
|
||||||
5. **Multiple thresholds** - Different minimum block durations for different scopes reduces noise
|
5. **Multiple thresholds** - Different minimum block durations for different scopes reduces noise
|
||||||
|
|
||||||
|
### UI/UX Design
|
||||||
|
|
||||||
|
**Dark Mode Theme** (applied across all views):
|
||||||
|
- Background: `#1a1a1a`
|
||||||
|
- Text: `#e0e0e0`
|
||||||
|
- Accent/links: `#6b9bd1`
|
||||||
|
- Borders: `#444`
|
||||||
|
- Component backgrounds: `#2a2a2a`
|
||||||
|
|
||||||
|
**Calendar View Features**:
|
||||||
|
- Hour-based aggregation (configurable: 1h, 3h, 6h grids)
|
||||||
|
- Overlapping task blocks cascade right and down (8% horizontal, 4px vertical per task)
|
||||||
|
- Only active workspaces shown (Plan/Think/Work) - idle time appears as gaps
|
||||||
|
- Task colors generated via hash-based HSL for consistency
|
||||||
|
- Height proportional to time spent in each grid period
|
||||||
|
|
||||||
|
**Switches View Features**:
|
||||||
|
- Cell heights proportional to switch duration (30px min, 200px max)
|
||||||
|
- Color-coded by task with hash-based HSL
|
||||||
|
- Dark backgrounds with reduced opacity for idle workspaces
|
||||||
|
|
||||||
|
**Navigation**:
|
||||||
|
- Consistent nav bar across all views: Today | Calendar | Switches | Work | All Time
|
||||||
|
- All views interconnected for easy movement between different data perspectives
|
||||||
|
|
||||||
|
## GNOME Extension
|
||||||
|
|
||||||
|
A GNOME Shell extension displays the current task in the top panel:
|
||||||
|
- Location: `gnome-extension/deskmeter-indicator@local/`
|
||||||
|
- Polls `/api/current_task` endpoint
|
||||||
|
- Updates on workspace switch with 2.2s debounce
|
||||||
|
- Automatically truncates long task paths
|
||||||
|
- Installation: `./gnome-extension/install.sh` or `./gnome-extension/update.sh`
|
||||||
|
|
||||||
|
## Key Implementation Details
|
||||||
|
|
||||||
|
**Calendar Aggregation** (`dmapp/dmweb/get_period_times.py:get_task_blocks_calendar()`):
|
||||||
|
- Aggregates switches by configurable time grid (1h, 3h, or 6h)
|
||||||
|
- Only includes active workspaces: Plan, Think, Work
|
||||||
|
- Returns blocks with task_id, task_path, start time, duration, and hour
|
||||||
|
- Filters out blocks shorter than `min_block_seconds`
|
||||||
|
|
||||||
|
**Task Path Resolution**:
|
||||||
|
- Primary: `tasks` collection (current tasks)
|
||||||
|
- Fallback: `task_history` collection (cached historical tasks)
|
||||||
|
- Last resort: On-demand file search in task directory
|
||||||
|
- Caching prevents repeated file I/O
|
||||||
|
|
||||||
|
**Switch Height Calculation** (switches view):
|
||||||
|
- Finds max delta in current view
|
||||||
|
- Heights scale linearly: `base_height + (ratio * (max_height - base_height))`
|
||||||
|
- Base: 30px, Max: 200px
|
||||||
|
|
||||||
## Tool Development Guidelines
|
## Tool Development Guidelines
|
||||||
|
|
||||||
- This is a personal tool, not professionally developed
|
- This is a personal tool, not professionally developed
|
||||||
- reuse as it is as much as possible unless refactor is required explicitly, suggest improvements
|
- Reuse existing code as much as possible unless refactor is required explicitly
|
||||||
- Only modify existing code if it absolutely doesn't make sense to add a new flow
|
- Only modify existing code if it absolutely doesn't make sense to add a new flow
|
||||||
|
- When adding features, maintain the dark mode theme consistency
|
||||||
|
- All new views should include the standard navigation bar
|
||||||
|
|||||||
@@ -1,23 +1,50 @@
|
|||||||
{% extends 'layout.html' %}
|
{% extends 'layout.html' %}
|
||||||
|
|
||||||
{% block head %}
|
{% block head %}
|
||||||
|
|
||||||
|
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
color: #e0e0e0;
|
||||||
|
margin: 20px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-bar {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
border-bottom: 2px solid #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-bar a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #6b9bd1;
|
||||||
|
font-size: 12pt;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-bar a:hover {
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
}
|
||||||
|
|
||||||
td > * {
|
td > * {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
vertical-align : top;
|
vertical-align: top;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
|
color: #e0e0e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 1px solid black;
|
border: 1px solid #444;
|
||||||
|
background-color: #2a2a2a;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
@@ -25,8 +52,16 @@ table {
|
|||||||
{% endblock head %}
|
{% endblock head %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{{ content | safe }}
|
<div class="nav-bar">
|
||||||
|
<a href="/">Today</a>
|
||||||
|
<a href="/calendar/daily">Calendar</a>
|
||||||
|
<a href="/switches/daily">Switches</a>
|
||||||
|
<a href="/work">Work</a>
|
||||||
|
<a href="/totals">All Time</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ content | safe }}
|
||||||
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
@@ -9,6 +9,23 @@
|
|||||||
color: #e0e0e0;
|
color: #e0e0e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-bar {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-bar a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #6b9bd1;
|
||||||
|
font-size: 12pt;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-bar a:hover {
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-tabs {
|
.nav-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
@@ -169,6 +186,13 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="nav-bar" style="margin-bottom: 15px; padding-bottom: 10px; border-bottom: 2px solid #444; font-size: 12pt;">
|
||||||
|
<a href="/">Today</a>
|
||||||
|
<a href="/switches/{{ scope }}/{{ base_date.year }}/{{ base_date.month }}/{{ base_date.day }}">Switches</a>
|
||||||
|
<a href="/work">Work</a>
|
||||||
|
<a href="/totals">All Time</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="nav-tabs">
|
<div class="nav-tabs">
|
||||||
<a href="/calendar/daily/{{ base_date.year }}/{{ base_date.month }}/{{ base_date.day }}?grid={{ grid }}"
|
<a href="/calendar/daily/{{ base_date.year }}/{{ base_date.month }}/{{ base_date.day }}?grid={{ grid }}"
|
||||||
class="{% if scope == 'daily' %}active{% endif %}">Daily</a>
|
class="{% if scope == 'daily' %}active{% endif %}">Daily</a>
|
||||||
@@ -184,7 +208,6 @@
|
|||||||
class="{% if grid == 3 %}active{% endif %}" style="font-size: 11pt;">3h</a>
|
class="{% if grid == 3 %}active{% endif %}" style="font-size: 11pt;">3h</a>
|
||||||
<a href="/calendar/{{ scope }}/{{ base_date.year }}/{{ base_date.month }}/{{ base_date.day }}?grid=6"
|
<a href="/calendar/{{ scope }}/{{ base_date.year }}/{{ base_date.month }}/{{ base_date.day }}?grid=6"
|
||||||
class="{% if grid == 6 %}active{% endif %}" style="font-size: 11pt;">6h</a>
|
class="{% if grid == 6 %}active{% endif %}" style="font-size: 11pt;">6h</a>
|
||||||
<a href="/switches/{{ scope }}/{{ base_date.year }}/{{ base_date.month }}/{{ base_date.day }}">View Switches</a>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,27 @@
|
|||||||
color: #e0e0e0;
|
color: #e0e0e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-bar {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
left: 20px;
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-bar a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #6b9bd1;
|
||||||
|
font-size: 11pt;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-bar a:hover {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
.grey {
|
.grey {
|
||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
@@ -57,6 +78,14 @@
|
|||||||
<!-- agregar función que me diga cuanto tiempo hace que esta activo el escritorio
|
<!-- agregar función que me diga cuanto tiempo hace que esta activo el escritorio
|
||||||
(calcular el delta con el ultimo switch y pasarlo a mm:ss) -->
|
(calcular el delta con el ultimo switch y pasarlo a mm:ss) -->
|
||||||
|
|
||||||
|
<div class="nav-bar">
|
||||||
|
<a href="/">Today</a>
|
||||||
|
<a href="/calendar/daily">Calendar</a>
|
||||||
|
<a href="/switches/daily">Switches</a>
|
||||||
|
<a href="/work">Work</a>
|
||||||
|
<a href="/totals">All Time</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="content-container">
|
<div id="content-container">
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% include 'main_content.html' %}
|
{% include 'main_content.html' %}
|
||||||
|
|||||||
@@ -1,16 +1,81 @@
|
|||||||
{% extends 'layout.html' %}
|
{% extends 'layout.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block head %}
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
color: #e0e0e0;
|
||||||
|
margin: 20px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-bar {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
border-bottom: 2px solid #444;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-bar a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #6b9bd1;
|
||||||
|
font-size: 12pt;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-bar a:hover {
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
font-size: 24pt;
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: auto;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 10px 40px 10px 0;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grey {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blue {
|
||||||
|
color: #6b9bd1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock head %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="nav-bar">
|
||||||
|
<a href="/">Today</a>
|
||||||
|
<a href="/calendar/daily">Calendar</a>
|
||||||
|
<a href="/switches/daily">Switches</a>
|
||||||
|
<a href="/work">Work</a>
|
||||||
|
<a href="/totals">All Time</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
{% for row in rows %}
|
{% for row in rows %}
|
||||||
|
{% if row["ws"] in ['Away', 'Other'] %}
|
||||||
|
{% set my_class = 'grey' %}
|
||||||
|
{% elif row["ws"] in ['Active', 'Idle'] %}
|
||||||
|
{% set my_class = 'blue' %}
|
||||||
|
{% else %}
|
||||||
|
{% set my_class = '' %}
|
||||||
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ row["ws"] }}</td>
|
<td class="{{ my_class }}">{{ row["ws"] }}</td>
|
||||||
<td>{{ row["total"] }}</td>
|
<td class="{{ my_class }}">{{ row["total"] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
@@ -9,6 +9,23 @@
|
|||||||
color: #e0e0e0;
|
color: #e0e0e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-bar {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-bar a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #6b9bd1;
|
||||||
|
font-size: 12pt;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-bar a:hover {
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-tabs {
|
.nav-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
@@ -122,6 +139,13 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="nav-bar" style="margin-bottom: 15px; padding-bottom: 10px; border-bottom: 2px solid #444;">
|
||||||
|
<a href="/">Today</a>
|
||||||
|
<a href="/calendar/{{ scope }}/{{ base_date.year }}/{{ base_date.month }}/{{ base_date.day }}">Calendar</a>
|
||||||
|
<a href="/work">Work</a>
|
||||||
|
<a href="/totals">All Time</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="nav-tabs">
|
<div class="nav-tabs">
|
||||||
<a href="/switches/daily/{{ base_date.year }}/{{ base_date.month }}/{{ base_date.day }}"
|
<a href="/switches/daily/{{ base_date.year }}/{{ base_date.month }}/{{ base_date.day }}"
|
||||||
class="{% if scope == 'daily' %}active{% endif %}">Daily</a>
|
class="{% if scope == 'daily' %}active{% endif %}">Daily</a>
|
||||||
@@ -129,9 +153,6 @@
|
|||||||
class="{% if scope == 'weekly' %}active{% endif %}">Weekly</a>
|
class="{% if scope == 'weekly' %}active{% endif %}">Weekly</a>
|
||||||
<a href="/switches/monthly/{{ base_date.year }}/{{ base_date.month }}/{{ base_date.day }}"
|
<a href="/switches/monthly/{{ base_date.year }}/{{ base_date.month }}/{{ base_date.day }}"
|
||||||
class="{% if scope == 'monthly' %}active{% endif %}">Monthly</a>
|
class="{% if scope == 'monthly' %}active{% endif %}">Monthly</a>
|
||||||
<span style="margin-left: auto;">
|
|
||||||
<a href="/calendar/{{ scope }}/{{ base_date.year }}/{{ base_date.month }}/{{ base_date.day }}">View Calendar</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="date-nav">
|
<div class="date-nav">
|
||||||
|
|||||||
Reference in New Issue
Block a user