aboutsummaryrefslogtreecommitdiff
path: root/brep/page.cxx
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2015-10-28 17:56:30 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2015-11-11 17:46:11 +0200
commit1ce84922e3008cad6cf1b9056b705f2642bd3772 (patch)
tree0dde62654e56c8e94ebf0cef83181ea9ddc99faf /brep/page.cxx
parent4993f11bf464c9aee0e3fd5965f4a8258cbe8b30 (diff)
WEB pages re-styling
Diffstat (limited to 'brep/page.cxx')
-rw-r--r--brep/page.cxx604
1 files changed, 486 insertions, 118 deletions
diff --git a/brep/page.cxx b/brep/page.cxx
index c68a7eb..a23ce17 100644
--- a/brep/page.cxx
+++ b/brep/page.cxx
@@ -4,6 +4,7 @@
#include <brep/page>
+#include <set>
#include <string>
#include <cassert>
#include <utility> // move()
@@ -12,209 +13,576 @@
#include <xml/serializer>
#include <web/xhtml>
+#include <web/mime-url-encoding>
#include <brep/package>
using namespace std;
using namespace xml;
+using namespace web;
using namespace web::xhtml;
namespace brep
{
- // A_STYLE
+ // CSS_LINKS
//
- void A_STYLE::
- operator() (xml::serializer& s) const
+ void CSS_LINKS::
+ operator() (serializer& s) const
{
- const char* ident ("\n ");
- s << "a {text-decoration: none;}" << ident
- << "a:hover {text-decoration: underline;}";
+ s << *LINK(REL="stylesheet", TYPE="text/css", HREF="/common.css")
+ << *LINK(REL="stylesheet", TYPE="text/css", HREF=url_);
}
- // DIV_PAGER
+ // DIV_HEADER
//
- DIV_PAGER::
- DIV_PAGER (size_t current_page,
- size_t item_count,
- size_t item_per_page,
- size_t page_number_count,
- const string& url)
- : current_page_ (current_page),
- item_count_ (item_count),
- item_per_page_ (item_per_page),
- page_number_count_ (page_number_count),
- url_ (url)
+ void DIV_HEADER::
+ operator() (serializer& s) const
{
+ s << DIV(ID="header")
+ << DIV(ID="header-menu")
+ << A(HREF="/") << "packages" << ~A
+ << A(HREF="/about") << "about" << ~A
+ << ~DIV
+ << ~DIV;
}
- void DIV_PAGER::
+ // FORM_SEARCH
+ //
+ void FORM_SEARCH::
operator() (serializer& s) const
{
- if (item_count_ == 0 || item_per_page_ == 0)
- return;
+ // The 'action' attribute is optional in HTML5. While the standard don't
+ // specify browser behavior explicitly for the case the attribute is
+ // ommited, the only reasonable behavior is to default it to the current
+ // document URL.
+ //
+ s << FORM(ID="search")
+ << TABLE(CLASS="form-table")
+ << TBODY
+ << TR
+ << TD(ID="search-txt")
+ << *INPUT(TYPE="search", NAME="q", VALUE=query_,
+ AUTOFOCUS="autofocus")
+ << ~TD
+ << TD(ID="search-btn")
+ << *INPUT(TYPE="submit", VALUE="Search")
+ << ~TD
+ << ~TR
+ << ~TBODY
+ << ~TABLE
+ << ~FORM;
+ }
- size_t pc (item_count_ / item_per_page_); // Page count.
+ // DIV_COUNTER
+ //
+ void DIV_COUNTER::
+ operator() (serializer& s) const
+ {
+ s << DIV(ID="count")
+ << count_ << " "
+ << (count_ % 10 == 1 && count_ % 100 != 11 ? singular_ : plural_)
+ << ~DIV;
+ }
- if (item_count_ % item_per_page_)
- ++pc;
+ // TR_NAME
+ //
+ void TR_NAME::
+ operator() (serializer& s) const
+ {
+ s << TR(CLASS="name")
+ << TH << "name" << ~TH
+ << TD
+ << SPAN(CLASS="value")
+ << A
+ << HREF
+ << "/go/" << mime_url_encode (name_);
+
+ // Propagate search criteria to the package details page.
+ //
+ if (!query_param_.empty ())
+ s << "?" << query_param_;
+
+ s << ~HREF
+ << name_
+ << ~A
+ << ~SPAN
+ << ~TD
+ << ~TR;
+ }
- if (pc > 1)
- {
- auto u (
- [this](size_t page) -> string
- {
- return page == 0
- ? url_
- : url_ + (url_.find ('?') == string::npos ? "?p=" : "&p=") +
- to_string (page);
- });
+ void TR_VERSION::
+ operator() (serializer& s) const
+ {
+ s << TR(CLASS="version")
+ << TH << "version" << ~TH
+ << TD
+ << SPAN(CLASS="value");
+
+ if (package_ == nullptr)
+ s << version_;
+ else
+ s << A
+ << HREF
+ << "/go/" << mime_url_encode (*package_) << "/" << version_
+ << ~HREF
+ << version_
+ << ~A;
+
+ s << ~SPAN
+ << ~TD
+ << ~TR;
+ }
- // Can consider customizing class names if use-case appear.
- //
- s << DIV(CLASS="pager");
+ // TR_SUMMARY
+ //
+ void TR_SUMMARY::
+ operator() (serializer& s) const
+ {
+ s << TR(CLASS="summary")
+ << TH << "summary" << ~TH
+ << TD << SPAN(CLASS="value") << summary_ << ~SPAN << ~TD
+ << ~TR;
+ }
- if (current_page_ > 0)
- s << A(CLASS="pg-prev")
- << HREF << u (current_page_ - 1) << ~HREF
- << "<<"
- << ~A
- << " ";
+ // TR_LICENSE
+ //
+ void TR_LICENSE::
+ operator() (serializer& s) const
+ {
+ s << TR(CLASS="license")
+ << TH << "license" << ~TH
+ << TD
+ << SPAN(CLASS="value");
- if (page_number_count_)
+ for (const auto& la: licenses_)
{
- size_t offset (page_number_count_ / 2);
- size_t fp (current_page_ > offset ? current_page_ - offset : 0);
- size_t tp (min (fp + page_number_count_, pc));
+ if (&la != &licenses_[0])
+ s << " " << EM << "or" << ~EM << " ";
- for (size_t p (fp); p < tp; ++p)
+ bool m (la.size () > 1);
+
+ if (m)
+ s << "(";
+
+ for (const auto& l: la)
{
- if (p == current_page_)
- s << SPAN(CLASS="pg-cpage") << p + 1 << ~SPAN;
- else
- s << A(CLASS="pg-page")
- << HREF << u (p) << ~HREF
- << p + 1
- << ~A;
+ if (&l != &la[0])
+ s << " " << EM << "and" << ~EM << " ";
- s << " ";
+ s << l;
}
+
+ if (m)
+ s << ")";
}
- if (current_page_ < pc - 1)
- s << A(CLASS="pg-next")
- << HREF << u (current_page_ + 1) << ~HREF
- << ">>"
- << ~A;
+ s << ~SPAN
+ << ~TD
+ << ~TR;
+ }
- s << ~DIV;
+ // TR_LICENSES
+ //
+ void TR_LICENSES::
+ operator() (serializer& s) const
+ {
+ for (const auto& la: licenses_)
+ {
+ s << TR(CLASS="license")
+ << TH << "license" << ~TH
+ << TD
+ << SPAN(CLASS="value");
+
+ for (const auto& l: la)
+ {
+ if (&l != &la[0])
+ s << " " << EM << "and" << ~EM << " ";
+
+ s << l;
+ }
+
+ s << ~SPAN
+ << SPAN_COMMENT (la.comment)
+ << ~TD
+ << ~TR;
}
}
- // DIV_PAGER_STYLE
+ // TR_TAGS
//
- void DIV_PAGER_STYLE::
- operator() (xml::serializer& s) const
+ void TR_TAGS::
+ operator() (serializer& s) const
{
- const char* ident ("\n ");
- s << ".pager {margin: 0.5em 0 0;}" << ident
- << ".pg-prev {padding: 0 0.3em 0 0;}" << ident
- << ".pg-page {padding: 0 0.3em 0 0;}" << ident
- << ".pg-cpage {padding: 0 0.3em 0 0; font-weight: bold;}";
+ if (!tags_.empty ())
+ {
+ s << TR(CLASS="tags")
+ << TH << "tags" << ~TH
+ << TD
+ << SPAN(CLASS="value");
+
+ for (const auto& t: tags_)
+ {
+ if (&t != &tags_[0])
+ s << " ";
+
+ s << A << HREF << "/?q=" << mime_url_encode (t) << ~HREF << t << ~A;
+ }
+
+ s << ~SPAN
+ << ~TD
+ << ~TR;
+ }
}
- // DIV_LICENSES
+ // TR_DEPENDS
//
- void DIV_LICENSES::
+ void TR_DEPENDS::
operator() (serializer& s) const
{
- s << DIV(CLASS="licenses")
- << "Licenses: ";
+ s << TR(CLASS="depends")
+ << TH << "depends" << ~TH
+ << TD
+ << SPAN(CLASS="value")
+ << dependencies_.size ();
- for (const auto& la: license_alternatives_)
+ if (!dependencies_.empty ())
+ s << "; ";
+
+ for (const auto& d: dependencies_)
{
- if (&la != &license_alternatives_[0])
- s << " | ";
+ if (&d != &dependencies_[0])
+ s << ", ";
- for (const auto& l: la)
+ if (d.conditional)
+ s << "?";
+
+ // Suppress package name duplicates.
+ //
+ set<string> ds;
+ for (const auto& da: d)
+ ds.emplace (da.name);
+
+ bool m (ds.size () > 1);
+
+ if (m)
+ s << "(";
+
+ bool first (true);
+ for (const auto& da: d)
{
- if (&l != &la[0])
- s << " & ";
+ if (ds.find (da.name) != ds.end ())
+ {
+ ds.erase (da.name);
- s << l;
+ if (first)
+ first = false;
+ else
+ s << " | ";
+
+ s << da.name; // @@ Make it a link.
+ }
}
+
+ if (m)
+ s << ")";
}
- s << ~DIV;
+ s << ~SPAN
+ << ~TD
+ << ~TR;
}
- // DIV_URL
+ // TR_REQUIRES
//
- void DIV_URL::
+ void TR_REQUIRES::
operator() (serializer& s) const
{
- s << DIV(CLASS="url")
- << A << HREF << url_ << ~HREF << url_ << ~A;
+ // If there are no requirements, then we omit it, unlike depends, where we
+ // show 0 explicitly.
+ //
+ if (requirements_.empty ())
+ return;
+
+ s << TR(CLASS="requires")
+ << TH << "requires" << ~TH
+ << TD
+ << SPAN(CLASS="value")
+ << requirements_.size () << "; ";
+
+ for (const auto& r: requirements_)
+ {
+ if (&r != &requirements_[0])
+ s << ", ";
- if (!url_.comment.empty ())
- s << DIV(CLASS="comment") << url_.comment << ~DIV;
+ if (r.conditional)
+ s << "?";
+
+ if (r.empty ())
+ {
+ // If there is no requirement alternatives specified, then
+ // print the comment first word.
+ //
+ const auto& c (r.comment);
+ if (!c.empty ())
+ {
+ auto n (c.find (' '));
+ s << string (c, 0, n);
+
+ if (n != string::npos)
+ s << "...";
+ }
+ }
+ else
+ {
+ bool m (r.size () > 1);
- s << ~DIV;
+ if (m)
+ s << "(";
+
+ for (const auto& ra: r)
+ {
+ if (&ra != &r[0])
+ s << " | ";
+
+ s << ra;
+ }
+
+ if (m)
+ s << ")";
+ }
+ }
+
+ s << ~SPAN
+ << ~TD
+ << ~TR;
+ }
+
+ // TR_URL
+ //
+ void TR_URL::
+ operator() (serializer& s) const
+ {
+ s << TR(CLASS=label_)
+ << TH << label_ << ~TH
+ << TD
+ << SPAN(CLASS="value") << A(HREF=url_) << url_ << ~A << ~SPAN
+ << SPAN_COMMENT (url_.comment)
+ << ~TD
+ << ~TR;
+ }
+
+ // TR_EMAIL
+ //
+ void TR_EMAIL::
+ operator() (serializer& s) const
+ {
+ s << TR(CLASS=label_)
+ << TH << label_ << ~TH
+ << TD
+ << SPAN(CLASS="value")
+ << A << HREF << "mailto:" << email_ << ~HREF << email_ << ~A
+ << ~SPAN
+ << SPAN_COMMENT (email_.comment)
+ << ~TD
+ << ~TR;
}
- // DIV_EMAIL
+ // TR_PRIORITY
//
- void DIV_EMAIL::
+ void TR_PRIORITY::
operator() (serializer& s) const
{
- s << DIV(CLASS="email")
- << A << HREF << "mailto:" << email_ << ~HREF << email_ << ~A;
+ static const strings priority_names ({"low", "medium", "high", "security"});
+ assert (priority_ < priority_names.size ());
- if (!email_.comment.empty ())
- s << DIV(CLASS="comment") << email_.comment << ~DIV;
+ s << TR(CLASS="priority")
+ << TH << "priority" << ~TH
+ << TD
+ << SPAN(CLASS="value") << priority_names[priority_] << ~SPAN
+ << SPAN_COMMENT (priority_.comment)
+ << ~TD
+ << ~TR;
+ }
- s << ~DIV;
+ // TR_LOCATION
+ //
+ void TR_LOCATION::
+ operator() (serializer& s) const
+ {
+ s << TR(CLASS="location")
+ << TH << "location" << ~TH
+ << TD << SPAN(CLASS="value") << location_ << ~SPAN << ~TD
+ << ~TR;
}
- // DIV_TAGS
+ // TR_DOWNLOAD
//
- void DIV_TAGS::
+ void TR_DOWNLOAD::
operator() (serializer& s) const
{
- if (!tags_.empty ())
+ s << TR(CLASS="download")
+ << TH << "download" << ~TH
+ << TD
+ << SPAN(CLASS="value") << A(HREF=url_) << url_ << ~A << ~SPAN
+ << ~TD
+ << ~TR;
+ }
+
+ // SPAN_COMMENT
+ //
+ void SPAN_COMMENT::
+ operator() (serializer& s) const
+ {
+ if (size_t l = comment_.size ())
+ s << SPAN(CLASS="comment")
+ << (comment_[l - 1] == '.' ? string (comment_, 0, l - 1) : comment_)
+ << ~SPAN;
+ }
+
+ // P_DESCRIPTION
+ //
+ void P_DESCRIPTION::
+ operator() (serializer& s) const
+ {
+ if (description_.empty ())
+ return;
+
+ string::size_type n (description_.find_first_of (" \t\n", length_));
+ bool f (n == string::npos); // Description length is below the limit.
+
+ // Truncate description if length exceed the limit.
+ //
+ const string& d (f ? description_ : string (description_, 0, n));
+
+ // Format the description into paragraphs, recognizing a blank line as
+ // paragraph separator, and replacing single newlines with a space.
+ //
+ s << P(ID="description");
+
+ bool nl (false); // The previous character is '\n'.
+ for (const auto& c: d)
{
- s << DIV(CLASS="tags")
- << "Tags: ";
+ if (c == '\n')
+ {
+ if (nl)
+ {
+ s << ~P << P;
+ nl = false;
+ }
+ else
+ nl = true; // Delay printing until the next character.
+ }
+ else
+ {
+ if (nl)
+ {
+ s << ' '; // Replace the previous newline with a space.
+ nl = false;
+ }
- for (const auto& t: tags_)
- s << t << " ";
+ s << c;
+ }
+ }
- s << ~DIV;
+ if (!f)
+ {
+ assert (url_ != nullptr);
+ s << "... " << A(HREF=*url_) << "More" << ~A;
}
+
+ s << ~P;
}
- // DIV_PRIORITY
+ // PRE_CHANGES
//
- void DIV_PRIORITY::
+ void PRE_CHANGES::
operator() (serializer& s) const
{
- static const strings priority_names (
- {"low", "medium", "high", "security"});
+ if (changes_.empty ())
+ return;
- assert (priority_ < priority_names.size ());
+ string::size_type n (changes_.find_first_of (" \t\n", length_));
+ bool f (n == string::npos); // Changes length is below the limit.
- s << DIV(CLASS="priority")
- << "Priority: " << priority_names[priority_]
- << ~DIV;
+ // Truncate changes if length exceed the limit.
+ //
+ const string& c (f ? changes_ : string (changes_, 0, n));
+ s << PRE(ID="changes") << c;
+
+ if (!f)
+ {
+ assert (url_ != nullptr);
+ s << "... " << A(HREF=*url_) << "More" << ~A;
+ }
+
+ s << ~PRE;
}
- // FORM_SEARCH
+ // DIV_PAGER
//
- void FORM_SEARCH::
+ DIV_PAGER::
+ DIV_PAGER (size_t current_page,
+ size_t item_count,
+ size_t item_per_page,
+ size_t page_number_count,
+ const string& url)
+ : current_page_ (current_page),
+ item_count_ (item_count),
+ item_per_page_ (item_per_page),
+ page_number_count_ (page_number_count),
+ url_ (url)
+ {
+ }
+
+ void DIV_PAGER::
operator() (serializer& s) const
{
- s << FORM
- << *INPUT(TYPE="search", NAME="q", VALUE=query_)
- << *INPUT(TYPE="submit", VALUE="Search")
- << ~FORM;
+ if (item_count_ == 0 || item_per_page_ == 0)
+ return;
+
+ size_t pc (item_count_ / item_per_page_); // Page count.
+
+ if (item_count_ % item_per_page_)
+ ++pc;
+
+ if (pc > 1)
+ {
+ auto u (
+ [this](size_t page) -> string
+ {
+ return page == 0
+ ? url_
+ : url_ + (url_.find ('?') == string::npos ? "?p=" : "&p=") +
+ to_string (page);
+ });
+
+ s << DIV(ID="pager");
+
+ if (current_page_ > 0)
+ s << A(ID="prev", HREF=u (current_page_ - 1)) << "Prev" << ~A;
+
+ if (page_number_count_)
+ {
+ size_t offset (page_number_count_ / 2);
+ size_t fp (current_page_ > offset ? current_page_ - offset : 0);
+ size_t tp (min (fp + page_number_count_, pc));
+
+ for (size_t p (fp); p < tp; ++p)
+ {
+ s << A(HREF=u (p));
+
+ if (p == current_page_)
+ s << ID << "curr" << ~ID;
+
+ s << p + 1
+ << ~A;
+ }
+ }
+
+ if (current_page_ < pc - 1)
+ s << A(ID="next", HREF=u (current_page_ + 1)) << "Next" << ~A;
+
+ s << ~DIV;
+ }
}
}