diff options
Diffstat (limited to 'brep/page.cxx')
-rw-r--r-- | brep/page.cxx | 604 |
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; + } } } |