Compare commits
11 Commits
276a77b67e
...
2ddb1281c1
Author | SHA1 | Date |
---|---|---|
Tim Van Baak | 2ddb1281c1 | |
Tim Van Baak | d8f37f27f8 | |
Tim Van Baak | 44d64e356c | |
Tim Van Baak | fc74673bb2 | |
Tim Van Baak | 8bd8bdf269 | |
Tim Van Baak | f626c86f8a | |
Tim Van Baak | a51a60c171 | |
Tim Van Baak | e8fca58e71 | |
Tim Van Baak | c263d8b469 | |
Tim Van Baak | 396f8d405a | |
Tim Van Baak | 606537d959 |
|
@ -1,9 +1,9 @@
|
||||||
"""
|
"""
|
||||||
Logic for operations that depend on a whole collection of documents.
|
Logic for managing documents.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from redstring.parser import load, TagOptions, DocumentTag, TabOptions, DocumentTab, Document
|
from redstring.parser import load, DocumentTag, DocumentTab, Document, TabOptions, TagOptions
|
||||||
|
|
||||||
|
|
||||||
def generate_index_document(directory: str) -> Document:
|
def generate_index_document(directory: str) -> Document:
|
||||||
|
@ -17,16 +17,18 @@ def generate_index_document(directory: str) -> Document:
|
||||||
document: Document = load(f)
|
document: Document = load(f)
|
||||||
|
|
||||||
# Check if this document specifies a tab, and create it if necessary.
|
# Check if this document specifies a tab, and create it if necessary.
|
||||||
category = document.get_tag('category')
|
if category_tag := document.get_tag('category'):
|
||||||
if not category:
|
category = category_tag.value
|
||||||
|
else:
|
||||||
category = 'index'
|
category = 'index'
|
||||||
if category not in categories:
|
if category not in categories:
|
||||||
categories[category] = {}
|
categories[category] = {}
|
||||||
category_tab = categories[category]
|
category_tab = categories[category]
|
||||||
|
|
||||||
# Check if this document specifies a topic, and create it if necessary.
|
# Check if this document specifies a topic, and create it if necessary.
|
||||||
topic = document.get_tag('topic')
|
if topic_tag := document.get_tag('topic'):
|
||||||
if not topic:
|
topic = topic_tag.value
|
||||||
|
else:
|
||||||
topic = 'uncategorized'
|
topic = 'uncategorized'
|
||||||
if '.' in topic:
|
if '.' in topic:
|
||||||
topic, subtopic = topic.split('.', maxsplit=1)
|
topic, subtopic = topic.split('.', maxsplit=1)
|
||||||
|
@ -62,8 +64,36 @@ def generate_index_document(directory: str) -> Document:
|
||||||
docs = sorted(categories[category][topic], key=lambda x: x[0])
|
docs = sorted(categories[category][topic], key=lambda x: x[0])
|
||||||
doc_links = map(document_link, docs)
|
doc_links = map(document_link, docs)
|
||||||
value = '- ' + '<br>- '.join(doc_links)
|
value = '- ' + '<br>- '.join(doc_links)
|
||||||
built_tags.append(DocumentTag(topic, value, TagOptions(), []))
|
built_tags.append(DocumentTag(topic, value))
|
||||||
|
|
||||||
built_tabs.append(DocumentTab(category, built_tags, TabOptions()))
|
built_tabs.append(DocumentTab(category, built_tags))
|
||||||
|
|
||||||
return Document(built_tabs)
|
return Document(built_tabs)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_default_document(doc_id) -> Document:
|
||||||
|
"""
|
||||||
|
Generate a blank document.
|
||||||
|
"""
|
||||||
|
return Document(tabs=[
|
||||||
|
DocumentTab(
|
||||||
|
name='tags',
|
||||||
|
tags=[
|
||||||
|
DocumentTag('id', doc_id),
|
||||||
|
DocumentTag('title', ''),
|
||||||
|
DocumentTag('author', ''),
|
||||||
|
DocumentTag('date', ''),
|
||||||
|
DocumentTag('source', ''),
|
||||||
|
DocumentTag('summary', ''),
|
||||||
|
DocumentTag('category', 'index', TagOptions(private=True)),
|
||||||
|
DocumentTag('topic', 'uncategorized', TagOptions(private=True)),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
DocumentTab(
|
||||||
|
name='notes',
|
||||||
|
tags=[
|
||||||
|
DocumentTag('notes', ''),
|
||||||
|
],
|
||||||
|
options=TabOptions(private=True, hide_names=True)
|
||||||
|
)
|
||||||
|
])
|
||||||
|
|
|
@ -4,7 +4,7 @@ Logic for reading and writing documents from files.
|
||||||
import argparse
|
import argparse
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import json
|
import json
|
||||||
from typing import Any, List, IO
|
from typing import Any, List, IO, Optional
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -23,6 +23,9 @@ class TabOptions:
|
||||||
def __init__(self, **kwargs) -> None:
|
def __init__(self, **kwargs) -> None:
|
||||||
self.options: dict = OrderedDict(**kwargs)
|
self.options: dict = OrderedDict(**kwargs)
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
return self.options
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def priority(self) -> int:
|
def priority(self) -> int:
|
||||||
"""Priority determines tab order."""
|
"""Priority determines tab order."""
|
||||||
|
@ -60,13 +63,10 @@ class TagOptions:
|
||||||
_PRIVATE_KEY = 'private'
|
_PRIVATE_KEY = 'private'
|
||||||
|
|
||||||
def __init__(self, **kwargs) -> None:
|
def __init__(self, **kwargs) -> None:
|
||||||
self.options = OrderedDict(**kwargs)
|
self.options: dict = OrderedDict(**kwargs)
|
||||||
# Tag value is a hyperlink
|
|
||||||
self.hyperlink: bool = kwargs.get('hyperlink', False)
|
def to_json(self):
|
||||||
# Tag value contains redstring interlinks
|
return self.options
|
||||||
self.interlink: bool = kwargs.get('interlink', False)
|
|
||||||
# Hide the tag from unauthenticated viewers
|
|
||||||
self.private: bool = kwargs.get('private', False)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hyperlink(self) -> bool:
|
def hyperlink(self) -> bool:
|
||||||
|
@ -98,10 +98,22 @@ class DocumentSubtag:
|
||||||
"""
|
"""
|
||||||
A keyvalue describing a document subject.
|
A keyvalue describing a document subject.
|
||||||
"""
|
"""
|
||||||
def __init__(self, name: str, value: str, options: TagOptions) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
value: str,
|
||||||
|
options: Optional[TagOptions] = None
|
||||||
|
) -> None:
|
||||||
self.name: str = name
|
self.name: str = name
|
||||||
self.value: str = value
|
self.value: str = value
|
||||||
self.options: TagOptions = options
|
self.options: TagOptions = options if options is not None else TagOptions()
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
return OrderedDict(
|
||||||
|
name=self.name,
|
||||||
|
value=self.value,
|
||||||
|
options=self.options.to_json(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DocumentTag:
|
class DocumentTag:
|
||||||
|
@ -112,23 +124,43 @@ class DocumentTag:
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
value: str,
|
value: str,
|
||||||
options: TagOptions,
|
options: Optional[TagOptions] = None,
|
||||||
subtags: List[DocumentSubtag]
|
subtags: Optional[List[DocumentSubtag]] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
self.name: str = name
|
self.name: str = name
|
||||||
self.value = value
|
self.value: str = value
|
||||||
self.options = options
|
self.options: TagOptions = options if options is not None else TagOptions()
|
||||||
self.subtags = subtags
|
self.subtags: list = subtags if subtags is not None else []
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
return OrderedDict(
|
||||||
|
name=self.name,
|
||||||
|
value=self.value,
|
||||||
|
options=self.options.to_json(),
|
||||||
|
subtags=[subtag.to_json() for subtag in self.subtags],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DocumentTab:
|
class DocumentTab:
|
||||||
"""
|
"""
|
||||||
A division of tags within a document.
|
A division of tags within a document.
|
||||||
"""
|
"""
|
||||||
def __init__(self, name: str, tags: List[DocumentTag], options: TabOptions) -> None:
|
def __init__(
|
||||||
self.name = name
|
self,
|
||||||
|
name: str,
|
||||||
|
tags: List[DocumentTag],
|
||||||
|
options: Optional[TabOptions] = None
|
||||||
|
) -> None:
|
||||||
|
self.name: str = name
|
||||||
self.tags: List[DocumentTag] = tags
|
self.tags: List[DocumentTag] = tags
|
||||||
self.options: TabOptions = options
|
self.options: TabOptions = options if options is not None else TabOptions()
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
return OrderedDict(
|
||||||
|
name=self.name,
|
||||||
|
tags=[tag.to_json() for tag in self.tags],
|
||||||
|
options=self.options.to_json(),
|
||||||
|
)
|
||||||
|
|
||||||
def get_tag(self, name: str):
|
def get_tag(self, name: str):
|
||||||
for tag in self.tags:
|
for tag in self.tags:
|
||||||
|
@ -136,6 +168,11 @@ class DocumentTab:
|
||||||
return tag
|
return tag
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_tag_value(self, name: str, default: str):
|
||||||
|
if tag := self.get_tag(name):
|
||||||
|
return tag.value
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
class Document:
|
class Document:
|
||||||
"""
|
"""
|
||||||
|
@ -144,6 +181,9 @@ class Document:
|
||||||
def __init__(self, tabs: List[DocumentTab]) -> None:
|
def __init__(self, tabs: List[DocumentTab]) -> None:
|
||||||
self.tabs: List[DocumentTab] = tabs
|
self.tabs: List[DocumentTab] = tabs
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
return [tab.to_json() for tab in self.tabs]
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return self.tabs.__iter__()
|
return self.tabs.__iter__()
|
||||||
|
|
||||||
|
@ -160,6 +200,11 @@ class Document:
|
||||||
return tag
|
return tag
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_tag_value(self, name: str, default: str):
|
||||||
|
if tag := self.get_tag(name):
|
||||||
|
return tag.value
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Parsing functions
|
# Parsing functions
|
||||||
|
@ -176,16 +221,6 @@ def load(fd: IO) -> Document:
|
||||||
return parse_document_from_json(parsed_json)
|
return parse_document_from_json(parsed_json)
|
||||||
|
|
||||||
|
|
||||||
def loads(string: str) -> Document:
|
|
||||||
"""
|
|
||||||
Load a document from a string.
|
|
||||||
"""
|
|
||||||
parsed_json = json.loads(string, object_pairs_hook=OrderedDict)
|
|
||||||
if not isinstance(parsed_json, list):
|
|
||||||
raise ValueError('Parsing as document, expected list')
|
|
||||||
return parse_document_from_json(parsed_json)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_document_from_json(parsed_json: list) -> Document:
|
def parse_document_from_json(parsed_json: list) -> Document:
|
||||||
"""
|
"""
|
||||||
Parses JSON into a Document object.
|
Parses JSON into a Document object.
|
||||||
|
@ -282,6 +317,14 @@ def parse_subtag_from_json(subtag_json: dict) -> DocumentSubtag:
|
||||||
return DocumentSubtag(name, value, options)
|
return DocumentSubtag(name, value, options)
|
||||||
|
|
||||||
|
|
||||||
|
def dump(doc: Document, fd: IO):
|
||||||
|
"""
|
||||||
|
Write a document to a file descriptor.
|
||||||
|
"""
|
||||||
|
dumped_json = doc.to_json()
|
||||||
|
json.dump(dumped_json, fd, indent=2)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# CLI functions
|
# CLI functions
|
||||||
#
|
#
|
||||||
|
|
|
@ -10,12 +10,13 @@ from flask import (
|
||||||
Flask,
|
Flask,
|
||||||
redirect,
|
redirect,
|
||||||
render_template,
|
render_template,
|
||||||
|
request,
|
||||||
safe_join,
|
safe_join,
|
||||||
url_for,
|
url_for,
|
||||||
)
|
)
|
||||||
|
|
||||||
from redstring.library import generate_index_document
|
from redstring.library import generate_index_document, generate_default_document
|
||||||
from redstring.parser import load
|
from redstring.parser import load, dump, DocumentTab, DocumentTag, TagOptions, DocumentSubtag
|
||||||
|
|
||||||
|
|
||||||
CONFIG_ENVVAR = 'REDSTRING_CONFIG'
|
CONFIG_ENVVAR = 'REDSTRING_CONFIG'
|
||||||
|
@ -23,18 +24,18 @@ CONFIG_ENVVAR = 'REDSTRING_CONFIG'
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/', methods=['GET'])
|
||||||
def root():
|
def root():
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/index/')
|
@app.route('/index/', methods=['GET'])
|
||||||
def index():
|
def index():
|
||||||
document = generate_index_document(current_app.config['root'])
|
document = generate_index_document(current_app.config['root'])
|
||||||
return render_template('doc.jinja', document=document, index=True)
|
return render_template('doc.jinja', document=document, index=True)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/doc/<document_id>')
|
@app.route('/doc/<document_id>', methods=['GET'])
|
||||||
def document(document_id):
|
def document(document_id):
|
||||||
doc_path = safe_join(current_app.config['root'], f'{document_id}.json')
|
doc_path = safe_join(current_app.config['root'], f'{document_id}.json')
|
||||||
if not os.path.isfile(doc_path):
|
if not os.path.isfile(doc_path):
|
||||||
|
@ -44,6 +45,57 @@ def document(document_id):
|
||||||
return render_template('doc.jinja', document=doc, index=False)
|
return render_template('doc.jinja', document=doc, index=False)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/new/', methods=['GET'])
|
||||||
|
def new():
|
||||||
|
document_id = 'new'
|
||||||
|
new_doc = generate_default_document(document_id)
|
||||||
|
doc_path = safe_join(current_app.config['root'], f'{document_id}.json')
|
||||||
|
with open(doc_path, 'w') as f:
|
||||||
|
dump(new_doc, f)
|
||||||
|
return redirect(url_for('document', document_id=document_id))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/edit/<document_id>', methods=['GET', 'POST'])
|
||||||
|
def edit(document_id):
|
||||||
|
# Load the document to edit
|
||||||
|
doc_path = safe_join(current_app.config['root'], f'{document_id}.json')
|
||||||
|
if not os.path.isfile(doc_path):
|
||||||
|
return abort(404)
|
||||||
|
with open(doc_path) as f:
|
||||||
|
doc = load(f)
|
||||||
|
|
||||||
|
# Check for structural change requests
|
||||||
|
if add := request.args.get('add'):
|
||||||
|
if add == 'tab':
|
||||||
|
new_tab = DocumentTab('newtab', [])
|
||||||
|
doc.tabs.append(new_tab)
|
||||||
|
with open(doc_path, 'w') as f:
|
||||||
|
dump(doc, f)
|
||||||
|
return redirect(url_for('edit', document_id=document_id))
|
||||||
|
elif add == 'tag':
|
||||||
|
if tab_name := request.args.get('tab'):
|
||||||
|
tab = doc.get_tab(tab_name)
|
||||||
|
new_tag = DocumentTag('tag', '', TagOptions(private=True))
|
||||||
|
tab.tags.append(new_tag)
|
||||||
|
with open(doc_path, 'w') as f:
|
||||||
|
dump(doc, f)
|
||||||
|
return redirect(url_for('edit', document_id=document_id))
|
||||||
|
return abort(400)
|
||||||
|
elif add == 'subtag':
|
||||||
|
if tag_name := request.args.get('tag'):
|
||||||
|
tag = doc.get_tag(tag_name)
|
||||||
|
new_subtag = DocumentSubtag('subtag', '', TagOptions(private=True))
|
||||||
|
tag.subtags.append(new_subtag)
|
||||||
|
with open(doc_path, 'w') as f:
|
||||||
|
dump(doc, f)
|
||||||
|
return redirect(url_for('edit', document_id=document_id))
|
||||||
|
return abort(400)
|
||||||
|
|
||||||
|
# Otherwise, return the editor page
|
||||||
|
else:
|
||||||
|
return render_template('edit.jinja', document=doc, index=False)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="Run the redstring server.")
|
parser = argparse.ArgumentParser(description="Run the redstring server.")
|
||||||
parser.add_argument("--config", help="Config file path.")
|
parser.add_argument("--config", help="Config file path.")
|
||||||
|
|
|
@ -101,12 +101,20 @@
|
||||||
table.page-table td:nth-child(2) {
|
table.page-table td:nth-child(2) {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
table.page-table td:nth-child(2) a {
|
table.page-table a {
|
||||||
color: #8af;
|
color: #8af;
|
||||||
}
|
}
|
||||||
table.page-table td:nth-child(2) a:visited {
|
table.page-table a:visited {
|
||||||
color: #88f;
|
color: #88f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Edit page styling */
|
||||||
|
input.tag-name {
|
||||||
|
}
|
||||||
|
textarea.tag-value {
|
||||||
|
resize: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% block page_scripts %}{% endblock %}
|
{% block page_scripts %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{% extends 'base.jinja' %}
|
{% extends 'base.jinja' %}
|
||||||
|
|
||||||
{% set page_title = 'tmp' -%}
|
{% set page_title = document.get_tag_value('title', document.get_tag('id').value) -%}
|
||||||
{% set page_summary = 'tmpp' %}
|
{% set page_summary = document.get_tag_value('summary', '') %}
|
||||||
|
|
||||||
{% block page_scripts %}
|
{% block page_scripts %}
|
||||||
<script>
|
<script>
|
||||||
|
@ -14,7 +14,7 @@ function selectTab(name) {
|
||||||
.forEach(e => e.classList.remove("tab-down"));
|
.forEach(e => e.classList.remove("tab-down"));
|
||||||
Array.from(document.getElementsByClassName("tab-page"))
|
Array.from(document.getElementsByClassName("tab-page"))
|
||||||
.forEach(e => e.classList.remove("tab-page-selected"));
|
.forEach(e => e.classList.remove("tab-page-selected"));
|
||||||
// Select the new tab and content
|
// Select the new tab and content
|
||||||
tab.classList.add("tab-down");
|
tab.classList.add("tab-down");
|
||||||
let content = document.getElementById(name + "-page");
|
let content = document.getElementById(name + "-page");
|
||||||
content.classList.add("tab-page-selected");
|
content.classList.add("tab-page-selected");
|
||||||
|
@ -38,22 +38,26 @@ window.onload = function () {
|
||||||
<div id="{{ tab.name }}-page" class="tab-page{% if selected %} tab-page-selected{% endif %}">
|
<div id="{{ tab.name }}-page" class="tab-page{% if selected %} tab-page-selected{% endif %}">
|
||||||
<table id="{{ tab.name }}-page-table" class="page-table">
|
<table id="{{ tab.name }}-page-table" class="page-table">
|
||||||
{% for tag in tab.tags %}
|
{% for tag in tab.tags %}
|
||||||
|
{%- if not tag.options.private -%}
|
||||||
<tr {% if tab.options.hide_names %}class="hide-tag-name"{% endif %}>
|
<tr {% if tab.options.hide_names %}class="hide-tag-name"{% endif %}>
|
||||||
<td>{{ tag.name }}</td>
|
<td>{{ tag.name }}</td>
|
||||||
<td>{{ make_tag_value(tag) }}</td>
|
<td>{{ make_tag_value(tag) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{%- endif -%}
|
||||||
{% for subtag in tag.subtags %}
|
{% for subtag in tag.subtags %}
|
||||||
|
{%- if not tag.options.private and not subtag.options.private -%}
|
||||||
<tr {% if tab.options.hide_names %}class="hide-tag-name"{% endif %}>
|
<tr {% if tab.options.hide_names %}class="hide-tag-name"{% endif %}>
|
||||||
<td>{% if loop.last %}└{% else %}├{% endif %} {{ subtag.name }}</td>
|
<td>{% if loop.last %}└{% else %}├{% endif %} {{ subtag.name }}</td>
|
||||||
<td>{{ make_tag_value(subtag) }}</td>
|
<td>{{ make_tag_value(subtag) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{%- endif -%}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{# TODO: tag.interlink and tag.private support #}
|
{# TODO: tag.interlink #}
|
||||||
{% macro make_tag_value(tag) -%}
|
{% macro make_tag_value(tag) -%}
|
||||||
{%- if tag.options.hyperlink -%}
|
{%- if tag.options.hyperlink -%}
|
||||||
<a href="{{ tag.value }}">{{ tag.value }}</a>
|
<a href="{{ tag.value }}">{{ tag.value }}</a>
|
||||||
|
@ -62,8 +66,19 @@ window.onload = function () {
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
{# TODO: tab.priority and tab.private support #}
|
{# TODO: tab.priority support #}
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
<div id="tabs">{% for tab in document %}{{ make_content_tab(tab, loop.first) }}{% endfor %}{% if not index %}<div id="index" class="tab tab-right"><a href="/index/">index</a></div>{% endif %}</div>
|
<div id="tabs">
|
||||||
{% for tab in document %}{{ make_tab_page(tab, loop.first) }}{% endfor %}
|
{%- for tab in document -%}
|
||||||
|
{%- if not tab.options.private -%}
|
||||||
|
{{ make_content_tab(tab, loop.first) }}
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endfor -%}
|
||||||
|
{%- if not index -%}
|
||||||
|
<div id="index" class="tab tab-right"><a href="/index/">index</a></div>
|
||||||
|
{%- endif -%}
|
||||||
|
</div>
|
||||||
|
{% for tab in document -%}
|
||||||
|
{{ make_tab_page(tab, loop.first) }}
|
||||||
|
{%- endfor -%}
|
||||||
{% endblock page_content %}
|
{% endblock page_content %}
|
|
@ -0,0 +1,135 @@
|
||||||
|
{% extends 'base.jinja' %}
|
||||||
|
|
||||||
|
{% set page_title = document.get_tag_value('title', document.get_tag('id').value) -%}
|
||||||
|
{% set page_summary = document.get_tag_value('summary', '') %}
|
||||||
|
|
||||||
|
{% block page_scripts %}
|
||||||
|
<script>
|
||||||
|
var newTabCounter = 0;
|
||||||
|
|
||||||
|
function selectTab(name) {
|
||||||
|
|
||||||
|
let tab = document.getElementById("tab-" + name);
|
||||||
|
if (tab)
|
||||||
|
{
|
||||||
|
// Unselect all tabs and content
|
||||||
|
Array.from(document.getElementsByClassName("tab-content"))
|
||||||
|
.forEach(e => e.classList.remove("tab-down"));
|
||||||
|
Array.from(document.getElementsByClassName("tab-page"))
|
||||||
|
.forEach(e => e.classList.remove("tab-page-selected"));
|
||||||
|
// Select the new tab and content
|
||||||
|
tab.classList.add("tab-down");
|
||||||
|
let content = document.getElementById(name + "-page");
|
||||||
|
content.classList.add("tab-page-selected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTab() {
|
||||||
|
let newTabName = "newtab" + ++newTabCounter;
|
||||||
|
let tabsDiv = document.querySelector("#tabs");
|
||||||
|
let wrapper = document.querySelector("#wrapper");
|
||||||
|
let newTabTab = document.querySelector("#newtab");
|
||||||
|
|
||||||
|
// Add tab div
|
||||||
|
let newTab = document.createElement("div");
|
||||||
|
newTab.id = "tab-" + newTabName;
|
||||||
|
newTab.classList = "tab tab-content";
|
||||||
|
newTab.contenteditable = true;
|
||||||
|
newTab.innerText = newTabName;
|
||||||
|
newTab.onclick = function() { selectTab(newTabName); };
|
||||||
|
tabsDiv.insertBefore(newTab, newTabTab);
|
||||||
|
|
||||||
|
// Add page div
|
||||||
|
let newPage = document.createElement("div");
|
||||||
|
newPage.id = newTabName + "-page";
|
||||||
|
newPage.classList = "tag-page";
|
||||||
|
let newPageTable = document.createElement("table");
|
||||||
|
newPageTable.id = newTabName + "-page-table";
|
||||||
|
newPageTable.classList = "page-table";
|
||||||
|
wrapper.appendChild(newPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function () {
|
||||||
|
// Respect fragment as a tab selector shortcut
|
||||||
|
if (window.location.hash) {
|
||||||
|
selectTab(window.location.hash.substring(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock page_scripts %}
|
||||||
|
|
||||||
|
{% macro make_content_tab(tab, selected) -%}
|
||||||
|
<div id="tab-{{ tab.name }}" class="tab tab-content{% if selected %} tab-down{% endif %}{% if index %} tab-right{% endif %}" onclick="javascript:selectTab('{{ tab.name }}')">{{ tab.name }}</div>
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{% macro make_tab_page(tab, selected) %}
|
||||||
|
<div id="{{ tab.name }}-page" class="tab-page{% if selected %} tab-page-selected{% endif %}">
|
||||||
|
<table id="{{ tab.name }}-page-table" class="page-table">
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><i>tab name</i></td>
|
||||||
|
<td><div contenteditable>{{ tab.name }}</div></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{% for tag in tab.tags %}
|
||||||
|
<tr>
|
||||||
|
{%- if tag.name == 'id' -%}
|
||||||
|
<td>{{ tag.name }}</td>
|
||||||
|
{%- else -%}
|
||||||
|
<td><div contenteditable>{{ tag.name }}</div></td>
|
||||||
|
{% endif %}
|
||||||
|
<td><div contenteditable>{{ tag.value }}</div></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{%- if tag.subtags -%}
|
||||||
|
│
|
||||||
|
{%- else -%}
|
||||||
|
<a href="/edit/{{ document.get_tag('id').value }}?add=subtag&tag={{ tag.name }}">└ +</a>
|
||||||
|
{%- endif -%}</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{% for subtag in tag.subtags %}
|
||||||
|
<tr>
|
||||||
|
<td><div contenteditable>{{ subtag.name }}</div></td>
|
||||||
|
<td><div contenteditable>{{ subtag.value }}</div></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{%- if not loop.last -%}
|
||||||
|
│
|
||||||
|
{%- else -%}
|
||||||
|
<a href="/edit/{{ document.get_tag('id').value }}?add=subtag&tag={{ tag.name }}">└ +</a>
|
||||||
|
{%- endif -%}
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><a href="/edit/{{ document.get_tag('id').value }}?add=tag&tab={{ tab.name }}">Add tag</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{# TODO: tab.priority support #}
|
||||||
|
{% block page_content %}
|
||||||
|
<div id="tabs">
|
||||||
|
{%- for tab in document -%}
|
||||||
|
{{ make_content_tab(tab, loop.first) }}
|
||||||
|
{%- endfor -%}
|
||||||
|
<div id="newtab" class="tab"><a href="/edit/{{ document.get_tag('id').value }}?add=tab">+</div>
|
||||||
|
<div id="index" class="tab tab-right"><a href="/index/">index</a></div>
|
||||||
|
</div>
|
||||||
|
{% for tab in document -%}
|
||||||
|
{{ make_tab_page(tab, loop.first) }}
|
||||||
|
{%- endfor -%}
|
||||||
|
{% endblock page_content %}
|
Loading…
Reference in New Issue