Merge the I-D and RFC views by showing I-D information on RFCs too.

I-Ds that have been published as RFCs redirect to the RFC URL. Also
support alias URLs so e.g. /doc/bcpXXXX redirects to /doc/rfcXXXX.
 - Legacy-Id: 5285
This commit is contained in:
Ole Laursen 2013-01-17 13:28:42 +00:00
parent a6a0596be6
commit c0ecb78761
6 changed files with 141 additions and 224 deletions

View file

@ -217,3 +217,10 @@ def add_state_change_event(doc, by, prev_state, new_state, timestamp=None):
e.save()
return e
def prettify_std_name(n):
if re.match(r"(rfc|bcp|fyi|std)[0-9]{4}", n):
return n[:3].upper() + " " + n[3:]
else:
return n

View file

@ -53,7 +53,7 @@ from ietf.ietfworkflows.utils import get_full_info_for_draft
from ietf.doc.models import *
from ietf.doc.utils import *
from ietf.utils.history import find_history_active_at
from ietf.ietfauth.utils import user_is_person, has_role, role_required, is_authorized_in_doc_stream
from ietf.ietfauth.utils import *
def render_document_top(request, doc, tab, name):
tabs = []
@ -89,13 +89,20 @@ def document_main(request, name, rev=None):
if rev != None: # no support for old revisions at the moment
raise Http404()
return document_main_idrfc(request, name, tab="document")
# generic part
doc = get_object_or_404(Document, docalias__name=name)
# take care of possible redirections
aliases = DocAlias.objects.filter(document=doc).values_list("name", flat=True)
if doc.type_id == "draft" and not name.startswith("rfc"):
for a in aliases:
if a.startswith("rfc"):
return redirect("doc_view", name=a)
group = doc.group
if doc.type_id == 'conflrev':
conflictdoc = doc.relateddocument_set.get(relationship__slug='conflrev').target.document
conflictdoc = doc.related_that_doc('conflrev').get().document
revisions = []
for h in doc.history_set.order_by("time", "id"):
@ -135,10 +142,7 @@ def document_main(request, name, rev=None):
# specific document types
if doc.type_id == "draft":
filename = "%s-%s.txt" % (doc.name, doc.rev)
split_content = not request.GET.get('include_text')
if request.COOKIES.get("full_draft", "") == "on":
split = False
@ -153,27 +157,24 @@ def document_main(request, name, rev=None):
person__user=request.user)))
can_edit_iana_state = has_role(request.user, ("Secretariat", "IANA"))
if name.startswith("rfc"):
# RFC tab
rfc_number = name[3:] if name.startswith("") else None
draft_name = None
for a in aliases:
if a.startswith("draft"):
draft_name = a
rfc_number = name[3:]
rfc_aliases = [prettify_std_name(a) for a in aliases
if a.startswith("fyi") or a.startswith("std") or a.startswith("bcp")]
latest_revision = None
if doc.get_state_slug() == "rfc":
# content
filename = name + ".txt"
content = get_document_content(filename, os.path.join(settings.RFC_PATH, filename),
split_content, markup=True)
draft_name = doc.name if doc.name.startswith("draft") else None
def prettify_std_name(n):
if re.match(r"(rfc|bcp|fyi)[0-9]{4}", n):
return n[:3].upper() + " " + n[3:]
else:
return n
aliases = [prettify_std_name(a.name) for a in doc.docalias_set.filter(
models.Q(name__startswith="fyi") |
models.Q(name__startswith="std"))]
# file types
base_path = os.path.join(settings.RFC_PATH, name + ".")
possible_types = ["txt", "pdf", "ps"]
@ -198,34 +199,35 @@ def document_main(request, name, rev=None):
elif "txt" not in found_types:
content = "This RFC is not available in plain text format."
split_content = False
else:
filename = "%s-%s.txt" % (draft_name, doc.rev)
return render_to_response("idrfc/document_rfc.html",
dict(doc=doc,
top=top,
name=name,
content=content,
split_content=split_content,
rfc_number=rfc_number,
updates=[prettify_std_name(d.name) for d in doc.related_that_doc("updates")],
updated_by=[prettify_std_name(d.canonical_name()) for d in doc.related_that("updates")],
obsoletes=[prettify_std_name(d.name) for d in doc.related_that_doc("obs")],
obsoleted_by=[prettify_std_name(d.canonical_name()) for d in doc.related_that("obs")],
draft_name=draft_name,
aliases=aliases,
has_errata=doc.tags.filter(slug="errata"),
published=doc.latest_event(type="published_rfc"),
file_urls=file_urls,
# can_edit=can_edit,
# can_change_stream=can_change_stream,
# can_edit_stream_info=can_edit_stream_info,
# telechat=telechat,
# ballot_summary=ballot_summary,
# iesg_state=iesg_state,
),
context_instance=RequestContext(request))
content = get_document_content(filename, os.path.join(settings.INTERNET_DRAFT_PATH, filename),
split_content, markup=True)
content = get_document_content(filename, os.path.join(settings.INTERNET_DRAFT_PATH, filename),
split_content, markup=True)
# file types
base_path = os.path.join(settings.INTERNET_DRAFT_PATH, doc.name + "-" + doc.rev + ".")
possible_types = ["pdf", "xml", "ps"]
found_types = ["txt"] + [t for t in possible_types if os.path.exists(base_path + t)]
tools_base = "http://tools.ietf.org/"
if doc.get_state_slug() == "active":
base = "http://www.ietf.org/id/"
else:
base = tools_base + "id/"
file_urls = []
for t in found_types:
label = "plain text" if t == "txt" else t
file_urls.append((label, base + doc.name + "-" + doc.rev + "." + t))
if "pdf" not in found_types:
file_urls.append(("pdf", tools_base + "pdf/" + doc.name + "-" + doc.rev + ".pdf"))
file_urls.append(("html", tools_base + "html/" + doc.name + "-" + doc.rev))
# latest revision
latest_revision = doc.latest_event(NewRevisionDocEvent, type="new_revision")
# ballot
ballot_summary = None
@ -237,17 +239,14 @@ def document_main(request, name, rev=None):
# submission
submission = ""
if group.type_id == "individ":
if doc.stream_id and doc.stream_id != "ietf":
submission = doc.stream.name
else:
submission = "individual"
submission = "individual"
elif group.type_id == "area" and doc.stream_id == "ietf":
submission = "individual in %s area" % group.acronym
else:
elif group.type_id in ("rg", "wg"):
submission = "%s %s" % (group.acronym, group.type)
if group.type_id == "wg":
submission = "<a href=\"%s\">%s</a>" % (urlreverse("wg_docs", kwargs=dict(acronym=doc.group.acronym)), submission)
if doc.get_state_slug("draft-stream-%s" % doc.stream_id) == "c-adopt":
if doc.stream_id and doc.get_state_slug("draft-stream-%s" % doc.stream_id) == "c-adopt":
submission = "candidate for %s" % submission
# resurrection
@ -257,32 +256,11 @@ def document_main(request, name, rev=None):
if e and e.type == "requested_resurrect":
resurrected_by = e.by
# file types
base_path = os.path.join(settings.INTERNET_DRAFT_PATH, doc.name + "-" + doc.rev + ".")
possible_types = ["pdf", "xml", "ps"]
found_types = ["txt"] + [t for t in possible_types if os.path.exists(base_path + t)]
tools_base = "http://tools.ietf.org/"
if doc.get_state_slug() == "active":
base = "http://www.ietf.org/id/"
else:
base = tools_base + "id/"
file_urls = []
for t in found_types:
label = "plain text" if t == "txt" else t
file_urls.append((label, base + doc.name + "-" + doc.rev + "." + t))
if "pdf" not in found_types:
file_urls.append(("pdf", tools_base + "pdf/" + doc.name + "-" + doc.rev + ".pdf"))
file_urls.append(("html", tools_base + "html/" + doc.name + "-" + doc.rev))
# stream info
stream_state = None
if doc.stream:
stream_state = doc.get_state("draft-stream-%s" % doc.stream_id)
stream_tags = get_tags_for_stream_id(doc.stream_id)
stream_tags = doc.tags.filter(slug__in=get_tags_for_stream_id(doc.stream_id))
shepherd_writeup = doc.latest_event(WriteupDocEvent, type="changed_protocol_writeup")
@ -329,12 +307,15 @@ def document_main(request, name, rev=None):
return render_to_response("idrfc/document_draft.html",
dict(doc=doc,
group=group,
top=top,
name=name,
content=content,
split_content=split_content,
revisions=revisions,
snapshot=snapshot,
latest_revision=latest_revision,
can_edit=can_edit,
can_change_stream=can_change_stream,
can_edit_stream_info=can_edit_stream_info,
@ -342,14 +323,24 @@ def document_main(request, name, rev=None):
can_edit_intended_std_level=can_edit_intended_std_level(request.user, doc),
can_edit_consensus=can_edit_consensus(request.user, doc),
can_edit_iana_state=can_edit_iana_state,
rfc_number=rfc_number,
draft_name=draft_name,
telechat=telechat,
ballot_summary=ballot_summary,
group=group,
submission=submission,
resurrected_by=resurrected_by,
replaces=[d.name for d in doc.related_that_doc("replaces")],
replaced_by=[d.name for d in doc.related_that("replaces")],
updates=[prettify_std_name(d.name) for d in doc.related_that_doc("updates")],
updated_by=[prettify_std_name(d.canonical_name()) for d in doc.related_that("updates")],
obsoletes=[prettify_std_name(d.name) for d in doc.related_that_doc("obs")],
obsoleted_by=[prettify_std_name(d.canonical_name()) for d in doc.related_that("obs")],
conflict_reviews=conflict_reviews,
rfc_aliases=rfc_aliases,
has_errata=doc.tags.filter(slug="errata"),
published=doc.latest_event(type="published_rfc"),
file_urls=file_urls,
stream_state=stream_state,
stream_tags=stream_tags,

View file

@ -221,12 +221,14 @@ def urlize_ietf_docs(string, autoescape=None):
"""
if autoescape and not isinstance(string, SafeData):
string = escape(string)
print string
string = re.sub("(?<!>)(RFC ?)0{0,3}(\d+)", "<a href=\"/doc/rfc\\2/\">\\1\\2</a>", string)
string = re.sub("(?<!>)(BCP ?)0{0,3}(\d+)", "<a href=\"http://tools.ietf.org/html/bcp\\2/\">\\1\\2</a>", string)
string = re.sub("(?<!>)(STD ?)0{0,3}(\d+)", "<a href=\"http://tools.ietf.org/html/std\\2/\">\\1\\2</a>", string)
string = re.sub("(?<!>)(FYI ?)0{0,3}(\d+)", "<a href=\"http://tools.ietf.org/html/fyi\\2/\">\\1\\2</a>", string)
string = re.sub("(?<!>)(draft-[-0-9a-zA-Z._+]+)", "<a href=\"/doc/\\1/\">\\1</a>", string)
string = re.sub("(?<!>)(conflict-review-[-0-9a-zA-Z._+]+)", "<a href=\"/doc/\\1/\">\\1</a>", string)
print string
return mark_safe(string)
urlize_ietf_docs.is_safe = True
urlize_ietf_docs.needs_autoescape = True

View file

@ -2,60 +2,83 @@
{% load ietf_filters %}
{% block title %}{{ name }}-{{ doc.rev }}{% endblock %}
{% block title %}{% if doc.get_state_slug == "rfc" %}RFC {{ rfc_number }}{% else %}{{ name }}-{{ doc.rev }}{% endif %}{% endblock %}
{% block pagehead %}
<link rel="stylesheet" type="text/css" href="/css/doc.css"></link>
<meta name="description" content="{{ doc.title }} (Internet-Draft, {{ doc.time|date:"Y" }})" />
<meta name="description" content="{{ doc.title }} {% if doc.get_state_slug == "rfc" %}(RFC {{ rfc_number }}{% if published %}, {{ published.time|date:"F Y" }}{% endif %}{% if obsoleted_by %}; obsoleted by {{ obsoleted_by|join:", " }}{% endif %}){% else %}(Internet-Draft, {{ doc.time|date:"Y" }}){% endif %}" />
{% endblock %}
{% block content %}
{{ top|safe }}
{% comment %}
<div class="snapshots">
Snapshots:
<span class="revisions">
{% for rev in revisions %}
<a {% if rev != doc.rev %}href="{% url doc_view name=doc.name %}{% if not forloop.last %}{{ rev }}/{% endif %}"{% endif %}>{{ rev }}</a>
{% endfor %}
</span>
</div>
{% endcomment %}
<div class="ietf-box metabox">
<div>
{{ doc.get_state }} {% if not doc.get_state_slug == "rfc" %}Internet-Draft{% endif %}
({{ submission|safe }})
{% if resurrected_by %}- resurrect requested by {{ resurrected_by }}{% endif %}
</div>
<table id="metatable" width="100%">
<tr>
<td>Document Stream:</td>
<td><a {% if can_change_stream %} class="editlink" href="{% url doc_change_stream name=doc.name %}"{% endif %}>
{{ doc.stream|default:"No stream defined" }}</a></td>
<td>Document type:</td>
<td>
{% if doc.get_state_slug == "rfc" %}
RFC - {{ doc.std_level }}
({% if published %}{{ published.time|date:"F Y" }}{% else %}publication date unknown{% endif %}{% if has_errata %}; <a href="http://www.rfc-editor.org/errata_search.php?rfc={{ rfc_number }}" rel="nofollow">Errata</a>{% endif %})
{% if obsoleted_by %}<div>Obsoleted by {{ obsoleted_by|join:", "|urlize_ietf_docs }}</div>{% endif %}
{% if updated_by %}<div>Updated by {{ updated_by|join:", "|urlize_ietf_docs }}</div>{% endif %}
{% if obsoletes %}<div>Obsoletes {{ obsoletes|join:", "|urlize_ietf_docs }}</div>{% endif %}
{% if updates %}<div>Updates {{ updates|join:", "|urlize_ietf_docs }}</div>{% endif %}
{% if rfc_aliases %}<div>Also Known As {{ rfc_aliases|join:", "|urlize_ietf_docs }}</div>{% endif %}
{% if draft_name %}<div>Was <a href="/doc/{{ draft_name}}/">{{ draft_name }}</a> {% if submission %}({{ submission|safe }}){% endif %}</div>{% endif %}
{% else %}
{{ doc.get_state }} Internet-Draft {% if submission %}({{ submission|safe }}){% endif %}
{% if resurrected_by %}- resurrect requested by {{ resurrected_by }}{% endif %}
{% endif %}
{% if replaces %}<div>Replaces: {{ replaces|join:", "|urlize_ietf_docs }}</div>{% endif %}
{% if replaced_by %}<div>Replaced by: {{ replaced_by|join:", "|urlize_ietf_docs }}</div>{% endif %}
</td>
</tr>
<tr>
<td>Document stream:</td>
<td>
<a {% if can_change_stream %} class="editlink" href="{% url doc_change_stream name=doc.name %}"{% endif %}>
{{ doc.stream|default:"No stream defined" }}
</a>
</td>
</tr>
<tr>
<td>Last updated:</td>
<td>{{ doc.time|date:"Y-m-d" }}</td>
<td>
{{ doc.time|date:"Y-m-d" }}
{% if latest_revision and latest_revision.time.date != doc.time.date %}
(latest revision {{ latest_revision.time|date:"Y-m-d" }})
{% endif %}
</td>
</tr>
{% if replaces %}
{% if doc.get_state_slug != "rfc" %}
<tr>
<td>Replaces:</td>
<td>{{ replaces|join:", "|urlize_ietf_docs }}</td>
<td>Intended RFC status:</td>
<td>
<a {% if can_edit %}class="editlink" href="{% url doc_change_intended_status name=doc.name %}"{% endif %}>
{{ doc.intended_std_level|default:"Unknown" }}</a>
</td>
</tr>
{% endif %}
{% if replaced_by %}
<tr>
<td>Replaced by:</td>
<td>{{ replaced_by|join:", "|urlize_ietf_docs }}</td>
<td>Other versions:</td>
<td>
{% if doc.get_state_slug != "active" and doc.get_state_slug != "rfc" %}(expired, archived):{% endif %}
{% if file_urls %}
{% for label, url in file_urls %}<a href="{{ url }}">{{ label}}</a>{% if not forloop.last%}, {% endif %}{% endfor %}
{% else %}
(not online)
{% endif %}
</td>
</tr>
{% endif %}
{% if conflict_reviews %}
<tr>
@ -64,22 +87,6 @@
</tr>
{% endif %}
<tr>
<td>Intended RFC status:</td>
<td>
<a {% if can_edit %}class="editlink" href="{% url doc_change_intended_status name=doc.name %}"{% endif %}>
{{ doc.intended_std_level|default:"(None)" }}</a>
</td>
</tr>
<tr>
<td>Other versions:</td>
<td>
{% if doc.get_state_slug != "active" %}(expired, archived):{% endif %}
{% for label, url in file_urls %}<a href="{{ url }}">{{ label}}</a>{% if not forloop.last%}, {% endif %}{% endfor %}
</td>
</tr>
<tr><td colspan='2'><hr size='1' noshade /></td></tr>
<tr>
@ -89,11 +96,13 @@
{{ stream_state|default:"(None)" }}
</a>
{% if stream_tags %}<br /><i>{% for tag in tags %}{{ tag.name }}{% if not forloop.last %}, {% endif %}{% endfor %}</i>{% endif %}
{% for m in milestones %}
<span title="In {{ m.group.acronym }} milestone: {{ m.desc }}" class="milestone">{{ m.due|date:"M Y" }}</span>
<span title="{{ m.desc }} ({{ m.group.acronym }} milestone)" class="milestone">{{ m.due|date:"M Y" }}</span>
{% endfor %}
{% if stream_tags %}
<div class="stream-tags">{% for tag in stream_tags %}{{ tag.name }}{% if not forloop.last %}, {% endif %}{% endfor %}</div>
{% endif %}
</td>
</tr>
@ -113,7 +122,7 @@
<td>
{# the shepherd edit page only works for WGs at the moment ... #}
<a {% if can_edit_stream_info and group.type_id == "wg" %}class="editlink" href="{% url doc_managing_shepherd acronym=group.acronym,name=doc.name %}"{% endif %}>
{{ doc.shepherd|default:"(None)" }}
{{ doc.shepherd|default:"No shepherd assigned" }}
</a>
</td>
</tr>
@ -210,9 +219,9 @@
<div class="links">
<a href="mailto:{{ doc.name }}@tools.ietf.org?subject=Mail%20regarding%20{{ doc.name }}" rel="nofollow">Email Authors</a>
| <a href="/ipr/search/?option=document_search&amp;id={{ doc.name }}" rel="nofollow">IPR Disclosures</a>
| <a href="http://www.fenron.net/~fenner/ietf/deps/index.cgi?dep={{ doc.name }}" rel="nofollow">Dependencies to this draft</a>
| <a href="http://www.fenron.net/~fenner/ietf/deps/index.cgi?dep={{ name }}" rel="nofollow">Dependencies to this document</a>
| <a href="http://tools.ietf.org/idnits?url=http://tools.ietf.org/id/{{ doc.filename_with_rev }}" rel="nofollow" target="_blank">Check nits</a>
| <a href="/feed/comments/{{ doc.name }}/">History feed</a>
| <a href="/feed/comments/{{ name }}/">History feed</a>
| <a href="http://www.google.com/search?as_q={{ doc.name }}&as_sitesearch={{ search_archive }}" rel="nofollow" target="_blank">Search Mailing Lists</a>
{% if user|has_role:"Area Director" %}
| <a href="https://www.iesg.org/bin/c5i?mid=6&rid=77&target={{ doc.name }}" rel="nofollow" target="_blank">Search Mailing Lists (ARO)</a>
@ -234,7 +243,7 @@
{% endif %}
</div>
{% if doc.get_state_slug == "active" %}
{% if doc.get_state_slug == "active" or doc.get_state_slug == "rfc" %}
<div class="document-markup">
{{ content|safe }}

View file

@ -1,92 +0,0 @@
{% extends "base.html" %}
{% load ietf_filters %}
{% block title %}RFC {{ rfc_number }}{% endblock %}
{% block pagehead %}
<link rel="stylesheet" type="text/css" href="/css/doc.css"></link>
<meta name="description" content="{{ doc.title }} (RFC {{ rfc_number }}{% if published %}, {{ published.time|date:"F Y" }}{% endif %}{% if obsoleted_by %}; obsoleted by {{ obsoleted_by|join:", " }}{% endif %})" />
{% endblock %}
{% block content %}
{{ top|safe }}
<div class="ietf-box metabox">
<table id="metatable" width="100%">
<tr>
<td>Document type:</td>
<td>
RFC - {{ doc.std_level }} {% if doc.stream %}({{ doc.stream.name }} stream){% endif %}
{% if obsoleted_by %}<br />Obsoleted by {{ obsoleted_by|join:", "|urlize_ietf_docs }}{% endif %}
{% if updated_by %}<br />Updated by {{ updated_by|join:", "|urlize_ietf_docs }}{% endif %}
{% if obsoletes %}<br />Obsoletes {{ obsoletes|join:", "|urlize_ietf_docs }}{% endif %}
{% if updates %}<br />Updates {{ updates|join:", "|urlize_ietf_docs }}{% endif %}
{% if aliases %}<br />Also Known As {{ aliases|join:", "|urlize_ietf_docs }}{% endif %}
{% if draft_name %}<br />Was <a href="/doc/{{ draft_name}}/">{{ draft_name }}</a>{% endif %}
{% if has_errata %}<br /><a href="http://www.rfc-editor.org/errata_search.php?rfc={{ rfc_number }}" rel="nofollow">Errata</a>{% endif %}
</td>
</tr>
<tr>
<td>Published:</td>
<td>{% if published %}{{ published.time|date:"F Y" }}{% else %}Unknown{% endif %}</td>
</tr>
{% comment %}
{% if doc.in_ietf_process %}
<tr class="post-rfc"><td colspan="2">* This information refers to IESG processing after the RFC was initially published</td></tr>
<tr class="post-rfc"><td><a href="/idtracker/help/state/">State</a>:</td><td>
{{ doc.friendly_state|safe }} (IESG: {{ doc.ietf_process.state}})
{% if doc.ietf_process.telechat_date %}<br/>On agenda of {{ doc.ietf_process.telechat_date }} IESG telechat {% if doc.ietf_process.telechat_returning_item %} (returning item){%endif%}{%endif%}
</td></tr>
<tr class="post-rfc"><td>Intended status:</td><td>{% if doc.in_ietf_process %}{{ doc.ietf_process.intended_maturity_level|default:"-" }}{% else %}-{%endif%}</td></tr>
<tr class="post-rfc"><td>Responsible AD:</td><td>{{ doc.ietf_process.ad_name|default:"-"|escape }}</td></tr>
{% if doc.ietf_process.iesg_note %}<tr class="post-rfc"><td>IESG Note:</td><td>{{ doc.ietf_process.iesg_note|escape }}</td></tr>{% endif %}
{% if user|in_group:"Area_Director,Secretariat" %}
<tr class="post-rfc"><td>Send notices to:</td><td>{{ doc.ietf_process.state_change_notice_to}}</td></tr>
{% endif %}{# if user|in_group:... #}
{% endif %}
{% endcomment %}
<tr>
<td>Other versions:</td>
<td>
{% if file_urls %}
{% for label, url in file_urls %}<a href="{{ url }}">{{ label}}</a>{% if not forloop.last%}, {% endif %}{% endfor %}
{% else %}
(not online)
{% endif %}
</td>
</tr>
<tr><td colspan='2'><hr size='1' noshade /></td></tr>
</table>
<div class="links">
<a href="/ipr/search/?option=rfc_search&amp;rfc_search={{ rfc_number }}" rel="nofollow">IPR Disclosures</a>
| <a href="http://www.fenron.net/~fenner/ietf/deps/index.cgi?dep={{ name }}" rel="nofollow">Dependencies to this RFC</a>
</div>
{% if can_edit and iesg_state %}
<div class="links">
<a href="{% url doc_ballot_lastcall name=doc.name %}">Last Call Text</a>
| <a href="{% url doc_ballot_writeupnotes name=doc.name %}">Ballot Text</a>
| <a href="{% url doc_ballot_approvaltext name=doc.name %}">Announcement Text</a>
</div>
{% endif %}
</div>
<div class="document-markup">
{{ content|safe }}
</div>
{% if split_content %}
<p><a style="display:inline-block;margin-left:17em;" href="?include_text=1">[include full document text]</a></p>
{% endif %}
{% endblock %}

View file

@ -3,6 +3,8 @@
#metatable tr { vertical-align: top; }
#metatable tr:first-child td:first-child { width: 15em; }
.stream-tags { font-style: italic; }
.document-markup pre { line-height: 1.2em; margin: 0; }
.document-markup .m_hdr, .m_ftr { color: #808080; }
.document-markup .m_ftr { border-bottom: 1px solid #a0a0a0; }
@ -40,8 +42,6 @@ a.editlink:visited {text-decoration:none; color:inherit;}
a.editlink:hover {text-decoration:underline;}
a.editlink:active {text-decoration:underline;}
h3 a.edit { font-weight: normal; font-size: 13px; display: inline-block; margin-left: 0.5em;}
h4 { margin-bottom: 0; }