From 1ce84922e3008cad6cf1b9056b705f2642bd3772 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 28 Oct 2015 17:56:30 +0200 Subject: WEB pages re-styling --- brep/buildfile | 2 +- brep/module.cxx | 28 +- brep/options.cli | 20 +- brep/package-details | 32 ++ brep/package-details.cxx | 225 +++++++++++++ brep/package-search.cxx | 86 +++-- brep/package-version-details.cxx | 267 +++++++-------- brep/package-version-search | 32 -- brep/package-version-search.cxx | 179 ---------- brep/page | 287 ++++++++++++++-- brep/page.cxx | 604 +++++++++++++++++++++++++++------- brep/services.cxx | 10 +- build.sh | 2 +- etc/httpd.conf | 14 +- etc/package-details.conf | 15 + etc/package-search.conf | 2 + etc/package-version-details.conf | 7 + etc/package-version-search.conf | 12 - tests/loader/driver.cxx | 33 +- tests/loader/internal/1/math/packages | 24 +- tests/web/xhtml/test.out | 1 + web/apache/request.cxx | 32 +- web/apache/service.cxx | 2 +- web/module | 8 +- web/xhtml | 25 +- www/common.css | 317 ++++++++++++++++++ www/package-details.css | 107 ++++++ www/package-search.css | 41 +++ www/package-version-details.css | 161 +++++++++ 29 files changed, 1915 insertions(+), 660 deletions(-) create mode 100644 brep/package-details create mode 100644 brep/package-details.cxx delete mode 100644 brep/package-version-search delete mode 100644 brep/package-version-search.cxx create mode 100644 etc/package-details.conf delete mode 100644 etc/package-version-search.conf create mode 100644 www/common.css create mode 100644 www/package-details.css create mode 100644 www/package-search.css create mode 100644 www/package-version-details.css diff --git a/brep/buildfile b/brep/buildfile index 79dadc2..808de15 100644 --- a/brep/buildfile +++ b/brep/buildfile @@ -21,7 +21,7 @@ libso{brep}: cxx.export.poptions = -I$out_root -I$src_root # import libs += libstudxml%lib{studxml} -brep = cxx{diagnostics module services package-search package-version-search \ +brep = cxx{diagnostics module services package-search package-details \ package-version-details shared-database page} cli.cxx{options} web = ../web/apache/cxx{request service} ../web/cxx{mime-url-encoding} diff --git a/brep/module.cxx b/brep/module.cxx index 62c70c9..fa7f479 100644 --- a/brep/module.cxx +++ b/brep/module.cxx @@ -89,7 +89,9 @@ namespace brep for (const auto& nv: options) { argv.push_back (nv.name.c_str ()); - argv.push_back (nv.value.c_str ()); + + if (nv.value) + argv.push_back (nv.value->c_str ()); } int argc (argv.size ()); @@ -252,7 +254,7 @@ namespace brep peek () { if (i_ != name_values_.end ()) - return name_ ? i_->name.c_str () : i_->value.c_str (); + return name_ ? i_->name.c_str () : i_->value->c_str (); else throw eos_reached (); } @@ -262,12 +264,8 @@ namespace brep { if (i_ != name_values_.end ()) { - const char* r (name_ ? i_->name.c_str () : i_->value.c_str ()); - - if (!name_) - ++i_; - - name_ = !name_; + const char* r (name_ ? i_->name.c_str () : i_->value->c_str ()); + skip (); return r; } else @@ -279,10 +277,18 @@ namespace brep { if (i_ != name_values_.end ()) { - if (!name_) + if (name_) + { + if (i_->value) + name_ = false; + else + ++i_; + } + else + { ++i_; - - name_ = !name_; + name_ = true; + } } else throw eos_reached (); diff --git a/brep/options.cli b/brep/options.cli index 90063c1..86cb70c 100644 --- a/brep/options.cli +++ b/brep/options.cli @@ -28,14 +28,17 @@ namespace brep std::uint16_t pages-in-pager = 10; }; - class package_version_search: module, db + class package_details: module, db { std::uint16_t results-on-page = 10; std::uint16_t pages-in-pager = 10; + std::uint16_t description-length = 400; }; class package_version_details: module, db { + std::uint16_t description-length = 400; + std::uint16_t changes-length = 800; }; } @@ -43,7 +46,8 @@ namespace brep // namespace params { - // Use parameters long names in the C++ code, short aliases in HTTP URL. + // Use parameters long names in the C++ code, short aliases (if present) + // in HTTP URL. // class package_search { @@ -56,7 +60,7 @@ namespace brep std::string query | q = ""; }; - class package_version_search + class package_details { // Display package version search result list starting from this page. // @@ -65,10 +69,20 @@ namespace brep // Package version search criteria. // std::string query | q = ""; + + // Full page variant. + // + // @@ Shouldn't we use one letter alias for URL as well ? + // I like full. It is descriptive. q= is kind now a convention. + // + bool full = false; }; class package_version_details { + // Full page variant. + // + bool full = false; }; } } diff --git a/brep/package-details b/brep/package-details new file mode 100644 index 0000000..471dcee --- /dev/null +++ b/brep/package-details @@ -0,0 +1,32 @@ +// file : brep/package-details -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BREP_PACKAGE_DETAILS +#define BREP_PACKAGE_DETAILS + +#include // shared_ptr + +#include // database + +#include +#include + +namespace brep +{ + class package_details: public module + { + private: + virtual void + handle (request&, response&); + + virtual void + init (cli::scanner&); + + private: + std::shared_ptr options_; + std::shared_ptr db_; + }; +} + +#endif // BREP_PACKAGE_DETAILS diff --git a/brep/package-details.cxx b/brep/package-details.cxx new file mode 100644 index 0000000..f814ef8 --- /dev/null +++ b/brep/package-details.cxx @@ -0,0 +1,225 @@ +// file : brep/package-details.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include // make_shared(), shared_ptr +#include // size_t + +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace std; +using namespace cli; +using namespace odb::core; + +namespace brep +{ + void package_details:: + init (scanner& s) + { + MODULE_DIAG; + + options_ = make_shared ( + s, unknown_mode::fail, unknown_mode::fail); + + db_ = shared_database (options_->db_host (), options_->db_port ()); + } + + template + static inline query + search_params (const string& n, const string& q) + { + using query = query; + + return "(" + + (q.empty () + ? query ("NULL") + : "plainto_tsquery (" + query::_val (q) + ")") + + "," + + query::_val (n) + + ")"; + } + + void package_details:: + handle (request& rq, response& rs) + { + using namespace xml; + using namespace web; + using namespace web::xhtml; + + MODULE_DIAG; + + const string& name (*rq.path ().rbegin ()); + params::package_details pr; + + try + { + param_scanner s (rq.parameters ()); + pr = params::package_details (s, unknown_mode::fail, unknown_mode::fail); + } + catch (const unknown_argument& e) + { + throw invalid_request (400, e.what ()); + } + + const string& sq (pr.query ()); // Search query. + size_t pg (pr.page ()); + bool f (pr.full ()); + string en (mime_url_encode (name)); + size_t rp (options_->results_on_page ()); + + auto url ( + [&en](bool f = false, + const string& q = "", + size_t p = 0, + const string& a = "") -> string + { + string s ("?"); + string u (en); + + if (f) { u += "?full"; s = "&"; } + if (!q.empty ()) { u += s + "q=" + mime_url_encode (q); s = "&"; } + if (p > 0) { u += s + "p=" + to_string (p); s = "&"; } + if (!a.empty ()) { u += '#' + a; } + return u; + }); + + serializer s (rs.content (), name); + const string title (sq.empty () ? name : name + " " + sq); + + s << HTML + << HEAD + << TITLE << title << ~TITLE + << CSS_LINKS ("/package-details.css") + << ~HEAD + << BODY + << DIV_HEADER () + << DIV(ID="content"); + + if (f) + s << CLASS << "full" << ~CLASS; + + s << DIV(ID="heading") + << H1 << A(HREF=url ()) << name << ~A << ~H1 + << A(HREF=url (!f, sq, pg)) << (f ? "[brief]" : "[full]") << ~A + << ~DIV; + + transaction t (db_->begin ()); + + shared_ptr p; + { + latest_package lp; + if (!db_->query_one ( + query( + "(" + query::_val (name) + ")"), lp)) + { + throw invalid_request (404, "Package '" + name + "' not found"); + } + + p = db_->load (lp.id); + } + + const license_alternatives& ll (p->license_alternatives); + + if (pg == 0) + { + // Display package details on the first page only. + // + s << H2 << p->summary << ~H2; + + if (const auto& d = p->description) + s << (f + ? P_DESCRIPTION (*d) + : P_DESCRIPTION ( + *d, + options_->description_length (), + url (!f, sq, pg, "description"))); + + s << TABLE(CLASS="proplist", ID="package") + << TBODY + << TR_LICENSE (ll) + << TR_URL (p->url) + << TR_EMAIL (p->email) + << TR_TAGS (p->tags) + << ~TBODY + << ~TABLE; + } + + auto pc ( + db_->query_value ( + search_params (name, sq))); + + auto r ( + db_->query ( + search_params (name, sq) + + "ORDER BY rank DESC, version_epoch DESC, " + "version_canonical_upstream DESC, version_revision DESC" + + "OFFSET" + to_string (pg * rp) + + "LIMIT" + to_string (rp))); + + s << FORM_SEARCH (sq.c_str ()) + << DIV_COUNTER (pc, "Version", "Versions") + + // Enclose the subsequent tables to be able to use nth-child CSS selector. + // + << DIV; + + for (const auto& pr: r) + { + shared_ptr p (db_->load (pr.id)); + + s << TABLE(CLASS="proplist version") + << TBODY + << TR_VERSION (name, p->version.string ()) + + // @@ Shouldn't we skip low priority row ? + // + << TR_PRIORITY (p->priority); + + // Comparing objects of the license_alternatives class as being of the + // vector> class, so comments are not considered. + // + if (p->license_alternatives != ll) + s << TR_LICENSE (p->license_alternatives); + + assert (p->internal_repository != nullptr); + + // @@ Shouldn't we make package location to be a link to the proper + // place of the About page, describing corresponding repository ? + // + // @@ In most cases package location will be the same for all versions + // of the same package. Shouldn't we put package location to the + // package summary part and display it here only if it differes + // from the one in the summary ? + // + s << TR_LOCATION (p->internal_repository.object_id ()) + << TR_DEPENDS (p->dependencies) + << TR_REQUIRES (p->requirements) + << ~TBODY + << ~TABLE; + } + + t.commit (); + + s << ~DIV + << DIV_PAGER (pg, pc, rp, options_->pages_in_pager (), url (f, sq)) + << ~DIV + << ~BODY + << ~HTML; + } +} diff --git a/brep/package-search.cxx b/brep/package-search.cxx index ffe5cb9..64e43f1 100644 --- a/brep/package-search.cxx +++ b/brep/package-search.cxx @@ -73,80 +73,68 @@ namespace brep throw invalid_request (400, e.what ()); } - // @@ Would be nice to have a manipulator indenting string properly - // according to the most nested element identation. - // - const char* ident ("\n "); - const char* title ("Package Search"); - serializer s (rs.content (), title); + const string& sq (pr.query ()); // Search query. + size_t pg (pr.page ()); + string qp (sq.empty () ? "" : "q=" + mime_url_encode (sq)); + size_t rp (options_->results_on_page ()); + + serializer s (rs.content (), "Packages"); + + const string& title ( + sq.empty () ? s.output_name () : s.output_name () + " " + sq); s << HTML << HEAD << TITLE << title << ~TITLE - << CSS_STYLE << ident - << A_STYLE () << ident - << DIV_PAGER_STYLE () << ident - << "#packages {font-size: x-large;}" << ident - << ".package {margin: 0.5em 0 0;}" << ident - << ".name {font-size: x-large;}" << ident - << ".tags {margin: 0.3em 0 0;}" << ident - << "form {margin: 0.5em 0 0 0;}" - << ~CSS_STYLE + << CSS_LINKS ("/package-search.css") << ~HEAD - << BODY; - - const string& sq (pr.query ()); // Search query. - string qp (sq.empty () ? "" : "q=" + mime_url_encode (sq)); - size_t rop (options_->results_on_page ()); + << BODY + << DIV_HEADER () + << DIV(ID="content"); transaction t (db_->begin ()); - size_t pc ( + auto pc ( db_->query_value ( search_param (sq))); - s << DIV(ID="packages") << "Packages (" << pc << ")" << ~DIV - << FORM_SEARCH (sq); - auto r ( db_->query ( search_param (sq) + "ORDER BY rank DESC, name" + - "OFFSET" + to_string (pr.page () * rop) + - "LIMIT" + to_string (rop))); + "OFFSET" + to_string (pg * rp) + + "LIMIT" + to_string (rp))); + + s << FORM_SEARCH (sq.c_str ()) + << DIV_COUNTER (pc, "Package", "Packages") + + // Enclose the subsequent tables to be able to use nth-child CSS selector. + // + << DIV; for (const auto& pr: r) { shared_ptr p (db_->load (pr.id)); - s << DIV(CLASS="package") - << DIV(CLASS="name") - << A - << HREF << "/go/" << mime_url_encode (p->id.name); - - // Propagate search criteria to the package version search url. - // - if (!qp.empty ()) - s << "?" << qp; - - s << ~HREF - << p->id.name - << ~A - << ~DIV - << DIV(CLASS="summary") << p->summary << ~DIV - << DIV_TAGS (p->tags) - << DIV_LICENSES (p->license_alternatives) - << DIV(CLASS="dependencies") - << "Dependencies: " << p->dependencies.size () - << ~DIV - << ~DIV; + s << TABLE(CLASS="proplist package") + << TBODY + << TR_NAME (p->id.name, qp) + << TR_SUMMARY (p->summary) + << TR_LICENSE (p->license_alternatives) + << TR_TAGS (p->tags) + << TR_DEPENDS (p->dependencies) + << TR_REQUIRES (p->requirements) + << ~TBODY + << ~TABLE; } t.commit (); - string u (qp.empty () ? "/" : ("/?" + qp)); + string url (qp.empty () ? "/" : ("/?" + qp)); - s << DIV_PAGER (pr.page (), pc, rop, options_->pages_in_pager (), u) + s << ~DIV + << DIV_PAGER (pg, pc, rp, options_->pages_in_pager (), url) + << ~DIV << ~BODY << ~HTML; } diff --git a/brep/package-version-details.cxx b/brep/package-version-details.cxx index 64945c4..71559b9 100644 --- a/brep/package-version-details.cxx +++ b/brep/package-version-details.cxx @@ -78,39 +78,44 @@ namespace brep throw invalid_request (400, e.what ()); } - const char* ident ("\n "); + bool f (pr.full ()); const string& vs (v.string ()); + + auto url ([&vs](bool f = false, const string& a = "") -> string + { + string u (vs); + + if (f) { u += "?full"; } + if (!a.empty ()) { u += '#' + a; } + return u; + }); + const string name (n + " " + vs); - const string title ("Package Version " + name); - serializer s (rs.content (), title); + serializer s (rs.content (), name); s << HTML << HEAD - << TITLE << title << ~TITLE - << CSS_STYLE << ident - << A_STYLE () << ident - << "#name {font-size: xx-large; font-weight: bold;}" << ident - << ".url, .email {font-size: medium;}" << ident - << ".comment {font-size: small;}" << ident - << "#summary {font-size: x-large; margin: 0.2em 0 0;}" << ident - << "#description {margin: 0.5em 0 0;}" << ident - << ".tags {margin: 0.3em 0 0;}" << ident - << "#package, .priority, #licenses, #dependencies, #requirements, " - "#locations, #changes {" << ident - << " font-size: x-large;" << ident - << " margin: 0.5em 0 0;" << ident - << "}" << ident - << "ul {margin: 0; padding: 0 0 0 1em;}" << ident - << "li {font-size: large; margin: 0.1em 0 0;}" << ident - << ".conditional {font-weight: bold;}" << ident - << "pre {font-size: medium; margin: 0.1em 0 0 1em;}" - << ~CSS_STYLE + << TITLE << name << ~TITLE + << CSS_LINKS ("/package-version-details.css") << ~HEAD << BODY - << DIV(ID="name") - << A << HREF << "/go/" << mime_url_encode (n) << ~HREF << n << ~A - << " " << vs - << ~DIV; + << DIV_HEADER () + << DIV(ID="content"); + + if (f) + s << CLASS << "full" << ~CLASS; + + s << DIV(ID="heading") + << H1 + << A + << HREF << "/go/" << mime_url_encode (n) << ~HREF + << n + << ~A + << "/" + << A(HREF=url ()) << vs << ~A + << ~H1 + << A(HREF=url (!f)) << (f ? "[brief]" : "[full]") << ~A + << ~DIV; bool not_found (false); shared_ptr p; @@ -134,182 +139,138 @@ namespace brep if (not_found) throw invalid_request (404, "Package '" + name + "' not found"); - assert (p->location); - const string u (p->internal_repository.load ()->location.string () + - "/" + p->location->string ()); - - s << DIV(CLASS="url") << A << HREF << u << ~HREF << u << ~A << ~DIV - << DIV(ID="summary") << p->summary << ~DIV - << DIV_URL (p->url) - << DIV_EMAIL (p->email); - - if (p->description) - s << DIV(ID="description") << *p->description << ~DIV; - - const priority& pt (p->priority); + s << H2 << p->summary << ~H2; - s << DIV_TAGS (p->tags) - << DIV_PRIORITY (pt); + if (const auto& d = p->description) + s << (f + ? P_DESCRIPTION (*d) + : P_DESCRIPTION ( + *d, options_->description_length (), url (!f, "description"))); - if (!pt.comment.empty ()) - s << DIV(CLASS="comment") << pt.comment << ~DIV; + // Link to download from the internal repository. + // + assert (p->location); + const string du (p->internal_repository.load ()->location.string () + + "/" + p->location->string ()); - const auto& ls (p->license_alternatives); + t.commit (); - s << DIV(ID="licenses") - << "Licenses:" - << UL; + s << TABLE(CLASS="proplist", ID="version") + << TBODY - for (const auto& la: ls) - { - s << LI; + // Repeat version here since it can be cut out in the header. + // + << TR_VERSION (p->version.string ()) - for (const auto& l: la) - { - if (&l != &la[0]) - s << " & "; + << TR_PRIORITY (p->priority) + << TR_LICENSES (p->license_alternatives) + << TR_LOCATION (p->internal_repository.object_id ()) + << TR_DOWNLOAD (du) + << ~TBODY + << ~TABLE - s << l; - } + << TABLE(CLASS="proplist", ID="package") + << TBODY + << TR_URL (p->url) + << TR_EMAIL (p->email); - if (!la.comment.empty ()) - s << DIV(CLASS="comment") << la.comment << ~DIV; + if (p->package_url && *p->package_url != p->url) + s << TR_URL (*p->package_url, "pkg-url"); - s << ~LI; - } + if (p->package_email && *p->package_email != p->email) + s << TR_EMAIL (*p->package_email, "pkg-email"); - s << ~UL - << ~DIV; + s << TR_TAGS (p->tags) + << ~TBODY + << ~TABLE; const auto& ds (p->dependencies); if (!ds.empty ()) { - s << DIV(ID="dependencies") - << "Dependencies:" - << UL; + s << H3 << "Depends" << ~H3 + << TABLE(CLASS="proplist", ID="depends") + << TBODY; for (const auto& da: ds) { - s << LI; + s << TR(CLASS="depends") + << TH; if (da.conditional) - s << SPAN(CLASS="conditional") << "? " << ~SPAN; + s << "?"; + + s << ~TH + << TD + << SPAN(CLASS="value"); for (const auto& d: da) { if (&d != &da[0]) s << " | "; - // @@ Should it be a link to the package version search page or - // the best matching package version details page on the - // corresponding repository site ? - // - s << d; + s << d; // @@ Should it be a link ? } - if (!da.comment.empty ()) - s << DIV(CLASS="comment") << da.comment << ~DIV; - - s << ~LI; + s << ~SPAN + << SPAN_COMMENT (da.comment) + << ~TD + << ~TR; } - s << ~UL - << ~DIV; + s << ~TBODY + << ~TABLE; } - const auto& rm (p->requirements); + const auto& rt (p->requirements); - if (!rm.empty ()) + if (!rt.empty ()) { - s << DIV(ID="requirements") - << "Requirements:" - << UL; + s << H3 << "Requires" << ~H3 + << TABLE(CLASS="proplist", ID="requires") + << TBODY; - for (const auto& ra: rm) + for (const auto& ra: rt) { - s << LI; + s << TR(CLASS="requires") + << TH; if (ra.conditional) - s << SPAN(CLASS="conditional") << "? " << ~SPAN; - - if (ra.empty ()) - // If there is no requirement alternatives specified, then - // print the comment instead. - // - s << ra.comment; - else - { - for (const auto& r: ra) - { - if (&r != &ra[0]) - s << " | "; - - s << r; - } - - if (!ra.comment.empty ()) - s << DIV(CLASS="comment") << ra.comment << ~DIV; - } - - s << ~LI; - } - - s << ~UL - << ~DIV; - } - - if (p->package_url || p->package_email) - { - s << DIV(ID="package") - << "Package:" - << UL; - - if (p->package_url) - s << LI << DIV_URL (*p->package_url) << ~LI; - - if (p->package_email) - s << LI << DIV_EMAIL (*p->package_email) << ~LI; - - s << ~UL - << ~DIV; - } - - const auto& er (p->external_repositories); + s << "?"; - if (!er.empty ()) - { - s << DIV(ID="locations") - << "Alternative Locations:" - << UL; + s << ~TH + << TD + << SPAN(CLASS="value"); - for (const auto& r: er) - { - repository_location l (move (r.load ()->location)); - assert (l.remote ()); + for (const auto& r: ra) + { + if (&r != &ra[0]) + s << " | "; - string u ("http://" + l.host ()); - if (l.port () != 0) - u += ":" + to_string (l.port ()); + s << r; + } - u += "/go/" + mime_url_encode (n) + "/" + vs; - s << LI - << DIV(CLASS="url") << A << HREF << u << ~HREF << u << ~A << ~DIV - << ~LI; + s << ~SPAN + << SPAN_COMMENT (ra.comment) + << ~TD + << ~TR; } - s << ~UL - << ~DIV; + s << ~TBODY + << ~TABLE; } - t.commit (); - const string& ch (p->changes); if (!ch.empty ()) - s << DIV(ID="changes") << "Changes:" << PRE << ch << ~PRE << ~DIV; - - s << ~BODY + s << H3 << "Changes" << ~H3 + << (f + ? PRE_CHANGES (ch) + : PRE_CHANGES ( + ch, options_->changes_length (), url (!f, "changes"))); + + s << ~DIV + << ~BODY << ~HTML; } } diff --git a/brep/package-version-search b/brep/package-version-search deleted file mode 100644 index 0292ae0..0000000 --- a/brep/package-version-search +++ /dev/null @@ -1,32 +0,0 @@ -// file : brep/package-version-search -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BREP_PACKAGE_VERSION_SEARCH -#define BREP_PACKAGE_VERSION_SEARCH - -#include // shared_ptr - -#include // database - -#include -#include - -namespace brep -{ - class package_version_search: public module - { - private: - virtual void - handle (request&, response&); - - virtual void - init (cli::scanner&); - - private: - std::shared_ptr options_; - std::shared_ptr db_; - }; -} - -#endif // BREP_PACKAGE_VERSION_SEARCH diff --git a/brep/package-version-search.cxx b/brep/package-version-search.cxx deleted file mode 100644 index 84c06e1..0000000 --- a/brep/package-version-search.cxx +++ /dev/null @@ -1,179 +0,0 @@ -// file : brep/package-version-search.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include - -#include -#include // make_shared(), shared_ptr -#include // size_t - -#include - -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -using namespace std; -using namespace cli; -using namespace odb::core; - -namespace brep -{ - void package_version_search:: - init (scanner& s) - { - MODULE_DIAG; - - options_ = make_shared ( - s, unknown_mode::fail, unknown_mode::fail); - - db_ = shared_database (options_->db_host (), options_->db_port ()); - } - - template - static inline query - search_params (const string& n, const string& q) - { - using query = query; - - return "(" + - (q.empty () - ? query ("NULL") - : "plainto_tsquery (" + query::_val (q) + ")") + - "," + - query::_val (n) + - ")"; - } - - void package_version_search:: - handle (request& rq, response& rs) - { - using namespace xml; - using namespace web; - using namespace web::xhtml; - - MODULE_DIAG; - - const string& name (*rq.path ().rbegin ()); - params::package_version_search pr; - - try - { - param_scanner s (rq.parameters ()); - pr = params::package_version_search ( - s, unknown_mode::fail, unknown_mode::fail); - } - catch (const unknown_argument& e) - { - throw invalid_request (400, e.what ()); - } - - const char* ident ("\n "); - const string title ("Package " + name); - serializer s (rs.content (), title); - - s << HTML - << HEAD - << TITLE << title << ~TITLE - << CSS_STYLE << ident - << A_STYLE () << ident - << DIV_PAGER_STYLE () << ident - << "#name {font-size: xx-large; font-weight: bold;}" << ident - << "#summary {font-size: x-large; margin: 0.2em 0 0;}" << ident - << ".url, .email {font-size: medium;}" << ident - << ".comment {font-size: small;}" << ident - << "#description {margin: 0.5em 0 0;}" << ident - << ".tags {margin: 0.3em 0 0;}" << ident - << "#versions {font-size: x-large; margin: 0.5em 0 0;}" << ident - << ".package_version {margin: 0.5em 0 0;}" << ident - << ".version {font-size: x-large;}" << ident - << ".priority {margin: 0.3em 0 0;}" << ident - << "form {margin: 0.5em 0 0 0;}" - << ~CSS_STYLE - << ~HEAD - << BODY - << DIV(ID="name") << name << ~DIV; - - const string& sq (pr.query ()); // Search query. - size_t rop (options_->results_on_page ()); - - transaction t (db_->begin ()); - - shared_ptr p; - { - latest_package lp; - if (!db_->query_one ( - query( - "(" + query::_val (name) + ")"), lp)) - { - throw invalid_request (404, "Package '" + name + "' not found"); - } - - p = db_->load (lp.id); - } - - s << DIV(ID="summary") << p->summary << ~DIV - << DIV_URL (p->url) - << DIV_EMAIL (p->email); - - if (p->description) - s << DIV(ID="description") << *p->description << ~DIV; - - s << DIV_TAGS (p->tags); - - size_t pvc ( - db_->query_value ( - search_params (name, sq))); - - s << DIV(ID="versions") << "Versions (" << pvc << ")" << ~DIV - << FORM_SEARCH (sq); - - auto r ( - db_->query ( - search_params (name, sq) + - "ORDER BY rank DESC, version_epoch DESC, " - "version_canonical_upstream DESC, version_revision DESC" + - "OFFSET" + to_string (pr.page () * rop) + - "LIMIT" + to_string (rop))); - - for (const auto& pr: r) - { - shared_ptr p (db_->load (pr.id)); - const string& v (p->version.string ()); - - s << DIV(CLASS="package_version") - << DIV(CLASS="version") - << A - << HREF << "/go/" << mime_url_encode (name) << "/" << v << ~HREF - << v - << ~A - << ~DIV - << DIV_PRIORITY (p->priority) - << DIV_LICENSES (p->license_alternatives) - << DIV(CLASS="dependencies") - << "Dependencies: " << p->dependencies.size () - << ~DIV - << ~DIV; - } - - t.commit (); - - string u (mime_url_encode (name)); - if (!sq.empty ()) - u += "?q=" + mime_url_encode (sq); - - s << DIV_PAGER (pr.page (), pvc, rop, options_->pages_in_pager (), u) - << ~BODY - << ~HTML; - } -} diff --git a/brep/page b/brep/page index b923c7f..309caa9 100644 --- a/brep/page +++ b/brep/page @@ -17,78 +17,150 @@ namespace brep // Page common building blocks. // - // A element default style. + // Generates CSS link elements. // - struct A_STYLE + class CSS_LINKS { + public: + CSS_LINKS (const char* u): url_ (u) {} + void operator() (xml::serializer& s) const; + + private: + const char* url_; }; - // Generates paging element. + // Generates page header element. // - class DIV_PAGER + struct DIV_HEADER + { + void + operator() (xml::serializer& s) const; + }; + + // Generates package search form element. + // + class FORM_SEARCH { public: - DIV_PAGER (std::size_t current_page, - std::size_t item_count, - std::size_t item_per_page, - std::size_t page_number_count, - const std::string& url); + FORM_SEARCH (const std::string& q): query_ (q) {} void operator() (xml::serializer& s) const; private: - std::size_t current_page_; - std::size_t item_count_; - std::size_t item_per_page_; - std::size_t page_number_count_; - const std::string& url_; + const std::string& query_; }; - // DIV_PAGER element default style. + // Generates counter element. + // + // It could be redunant to distinguish between singular and plural word forms + // if it wouldn't be so cheap in English, and phrase '1 Packages' wouldn't + // look that ugly. // - struct DIV_PAGER_STYLE + class DIV_COUNTER { + public: + DIV_COUNTER (std::size_t c, const char* s, const char* p) + : count_ (c), singular_ (s), plural_ (p) {} + void operator() (xml::serializer& s) const; + + private: + std::size_t count_; + const char* singular_; + const char* plural_; }; - // Generates url element. + // Generates package name element. // - class DIV_URL + class TR_NAME { public: - DIV_URL (const url& u): url_ (u) {} + TR_NAME (const std::string& n, const std::string& q) + : name_ (n), query_param_ (q) {} void operator() (xml::serializer& s) const; private: - const url& url_; + const std::string& name_; + const std::string& query_param_; }; - // Generates email element. + // Generates package version element. // - class DIV_EMAIL + class TR_VERSION { public: - DIV_EMAIL (const email& e): email_ (e) {} + // Display the version as a link to the package version details page. + // + TR_VERSION (const std::string& p, const std::string& v) + : package_ (&p), version_ (v) {} + + // Display the version as a regular text. + // + TR_VERSION (const std::string& v): package_ (nullptr), version_ (v) {} void operator() (xml::serializer& s) const; private: - const email& email_; + const std::string* package_; + const std::string& version_; + }; + + // Generates package summary element. + // + class TR_SUMMARY + { + public: + TR_SUMMARY (const std::string& s): summary_ (s) {} + + void + operator() (xml::serializer& s) const; + + private: + const std::string& summary_; + }; + + // Generates package license alternatives element. + // + class TR_LICENSE + { + public: + TR_LICENSE (const license_alternatives& l): licenses_ (l) {} + + void + operator() (xml::serializer& s) const; + + private: + const license_alternatives& licenses_; + }; + + // Generates package license alternatives elements. Differs from TR_LICENSE + // by producing multiple rows instead of a single one. + // + class TR_LICENSES + { + public: + TR_LICENSES (const license_alternatives& l): licenses_ (l) {} + + void + operator() (xml::serializer& s) const; + + private: + const license_alternatives& licenses_; }; // Generates package tags element. // - class DIV_TAGS + class TR_TAGS { public: - DIV_TAGS (const strings& ts): tags_ (ts) {} + TR_TAGS (const strings& ts): tags_ (ts) {} void operator() (xml::serializer& s) const; @@ -97,26 +169,71 @@ namespace brep const strings& tags_; }; - // Generates package version license alternatives element. + // Generates package dependencies element. // - class DIV_LICENSES + class TR_DEPENDS { public: - DIV_LICENSES (const license_alternatives& l): license_alternatives_ (l) {} + TR_DEPENDS (const dependencies& d): dependencies_ (d) {} void operator() (xml::serializer& s) const; private: - const license_alternatives& license_alternatives_; + const dependencies& dependencies_; + }; + + // Generates package requirements element. + // + class TR_REQUIRES + { + public: + TR_REQUIRES (const requirements& r): requirements_ (r) {} + + void + operator() (xml::serializer& s) const; + + private: + const requirements& requirements_; + }; + + // Generates url element. + // + class TR_URL + { + public: + TR_URL (const url& u, const char* l = "url"): url_ (u), label_ (l) {} + + void + operator() (xml::serializer& s) const; + + private: + const url& url_; + const char* label_; + }; + + // Generates email element. + // + class TR_EMAIL + { + public: + TR_EMAIL (const email& e, const char* l = "email") + : email_ (e), label_ (l) {} + + void + operator() (xml::serializer& s) const; + + private: + const email& email_; + const char* label_; }; // Generates package version priority element. // - class DIV_PRIORITY + class TR_PRIORITY { public: - DIV_PRIORITY (const priority& p): priority_ (p) {} + TR_PRIORITY (const priority& p): priority_ (p) {} void operator() (xml::serializer& s) const; @@ -125,18 +242,116 @@ namespace brep const priority& priority_; }; - // Generates package search element. + // Generates package location element. // - class FORM_SEARCH + class TR_LOCATION { public: - FORM_SEARCH (const std::string& q): query_ (q) {} + TR_LOCATION (const std::string& l): location_ (l) {} void operator() (xml::serializer& s) const; private: - const std::string& query_; + const std::string& location_; + }; + + // Generates package download URL element. + // + class TR_DOWNLOAD + { + public: + TR_DOWNLOAD (const std::string& u): url_ (u) {} + + void + operator() (xml::serializer& s) const; + + private: + const std::string& url_; + }; + + // Generates comment element. + // + class SPAN_COMMENT + { + public: + SPAN_COMMENT (const std::string& c): comment_ (c) {} + + void + operator() (xml::serializer& s) const; + + private: + const std::string& comment_; + }; + + // Generates package description element. + // + class P_DESCRIPTION + { + public: + // Genereate full description. + // + P_DESCRIPTION (const std::string& d) + : description_ (d), length_ (d.size ()), url_ (nullptr) {} + + // Genereate brief description. + // + P_DESCRIPTION (const std::string& d, size_t l, const std::string& u) + : description_ (d), length_ (l), url_ (&u) {} + + void + operator() (xml::serializer& s) const; + + private: + const std::string& description_; + std::size_t length_; + const std::string* url_; // Full page url. + }; + + // Generates package description element. + // + class PRE_CHANGES + { + public: + // Genereate full changes info. + // + PRE_CHANGES (const std::string& c) + : changes_ (c), length_ (c.size ()), url_ (nullptr) {} + + // Genereate brief changes info. + // + PRE_CHANGES (const std::string& c, size_t l, const std::string& u) + : changes_ (c), length_ (l), url_ (&u) {} + + void + operator() (xml::serializer& s) const; + + private: + const std::string& changes_; + std::size_t length_; + const std::string* url_; // Full page url. + }; + + // Generates paging element. + // + class DIV_PAGER + { + public: + DIV_PAGER (std::size_t current_page, + std::size_t item_count, + std::size_t item_per_page, + std::size_t page_number_count, + const std::string& url); + + void + operator() (xml::serializer& s) const; + + private: + std::size_t current_page_; + std::size_t item_count_; + std::size_t item_per_page_; + std::size_t page_number_count_; + const std::string& url_; }; } 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 +#include #include #include #include // move() @@ -12,209 +13,576 @@ #include #include +#include #include 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 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; + } } } diff --git a/brep/services.cxx b/brep/services.cxx index c022a7d..eb99f3d 100644 --- a/brep/services.cxx +++ b/brep/services.cxx @@ -7,7 +7,7 @@ #include #include -#include +#include #include using namespace brep; @@ -19,10 +19,10 @@ service AP_MODULE_DECLARE_DATA package_search_srv ( package_search_mod, {"db-host", "db-port", "conf"}); -static package_version_search package_version_search_mod; -service AP_MODULE_DECLARE_DATA package_version_search_srv ( - "package-version-search", - package_version_search_mod, +static package_details package_details_mod; +service AP_MODULE_DECLARE_DATA package_details_srv ( + "package-details", + package_details_mod, {"db-host", "db-port", "conf"}); static package_version_details package_version_details_mod; diff --git a/build.sh b/build.sh index 8a88216..77437db 100755 --- a/build.sh +++ b/build.sh @@ -32,7 +32,7 @@ cli --include-with-brackets --include-prefix brep --hxx-suffix "" \ echo "g++ libbrep-apache.so" -s="package-search.cxx package-version-search.cxx package-version-details.cxx \ +s="package-search.cxx package-details.cxx package-version-details.cxx \ module.cxx diagnostics.cxx page.cxx services.cxx options.cxx \ shared-database.cxx \ ../web/apache/request.cxx ../web/apache/service.cxx \ diff --git a/etc/httpd.conf b/etc/httpd.conf index bf1d9e2..33658a8 100644 --- a/etc/httpd.conf +++ b/etc/httpd.conf @@ -5,7 +5,7 @@ ServerAdmin "${AP_ADMIN_EMAIL}" User apache Group apache -DocumentRoot "${AP_WWW_DIR}/htdocs" +DocumentRoot "${AP_WWW_DIR}" CoreDumpDirectory "${AP_WORKSPACE_DIR}" PidFile "${AP_WORKSPACE_DIR}/httpd.pid" @@ -48,12 +48,12 @@ LoadModule package_search_srv ${AP_MODULE_DIR}/libbrep-apache.so package-search-conf "${AP_CONFIG_DIR}/package-search.conf" -LoadModule package_version_search_srv ${AP_MODULE_DIR}/libbrep-apache.so +LoadModule package_details_srv ${AP_MODULE_DIR}/libbrep-apache.so - - package-version-search-db-host ${AP_DB_HOST} - package-version-search-db-port ${AP_DB_PORT} - package-version-search-conf "${AP_CONFIG_DIR}/package-version-search.conf" + + package-details-db-host ${AP_DB_HOST} + package-details-db-port ${AP_DB_PORT} + package-details-conf "${AP_CONFIG_DIR}/package-details.conf" LoadModule package_version_details_srv ${AP_MODULE_DIR}/libbrep-apache.so @@ -69,7 +69,7 @@ LoadModule package_version_details_srv ${AP_MODULE_DIR}/libbrep-apache.so - SetHandler package-version-search + SetHandler package-details diff --git a/etc/package-details.conf b/etc/package-details.conf new file mode 100644 index 0000000..49b3dce --- /dev/null +++ b/etc/package-details.conf @@ -0,0 +1,15 @@ +# file : etc/package-details.conf +# copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file +# +# brep::module options +# +verb 1 + +# brep::package_details options +# +# @@ Set to 10 +results-on-page 2 +pages-in-pager 5 +# @@ Set to 500 (~ 80 chars x 6 lines) +description-length 100 diff --git a/etc/package-search.conf b/etc/package-search.conf index f7eb07c..80562b2 100644 --- a/etc/package-search.conf +++ b/etc/package-search.conf @@ -8,4 +8,6 @@ verb 1 # brep::package_search options # +# @@ Set to 10 results-on-page 2 +pages-in-pager 5 diff --git a/etc/package-version-details.conf b/etc/package-version-details.conf index 61a6fb9..84fd75d 100644 --- a/etc/package-version-details.conf +++ b/etc/package-version-details.conf @@ -5,3 +5,10 @@ # brep::module options # verb 1 + +# brep::package_version_details options +# +# @@ Set to 500 (~ 80 chars x 6 lines) +description-length 100 +# @@ Set to 5000 (~ 80 chars x 60 lines) +changes-length 100 diff --git a/etc/package-version-search.conf b/etc/package-version-search.conf deleted file mode 100644 index f7f0d37..0000000 --- a/etc/package-version-search.conf +++ /dev/null @@ -1,12 +0,0 @@ -# file : etc/package-version-search.conf -# copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -# license : MIT; see accompanying LICENSE file -# -# brep::module options -# -verb 1 - -# brep::package_version_search options -# -results-on-page 2 -pages-in-pager 10 diff --git a/tests/loader/driver.cxx b/tests/loader/driver.cxx index 6d9b950..f1e1385 100644 --- a/tests/loader/driver.cxx +++ b/tests/loader/driver.cxx @@ -320,9 +320,11 @@ main (int argc, char* argv[]) assert (fpv5->summary == "The Foo Math Library"); assert (fpv5->tags == strings ({"c++", "foo", "math"})); assert (*fpv5->description == - "A modern C++ library with easy to use linear algebra and " - "optimization tools. There are over 100 functions in total with " - "an extensive test suite. The API is similar to MATLAB."); + "A modern C++ library with easy to use linear algebra and lot of " + "optimization\ntools.\n\nThere are over 100 functions in total " + "with an extensive test suite. The API is\nsimilar to MATLAB." + "\n\nUseful for conversion of research code into production " + "environments."); assert (fpv5->url == "http://www.example.com/foo/"); assert (fpv5->package_url && *fpv5->package_url == "http://www.example.com/foo/pack"); @@ -335,7 +337,8 @@ main (int argc, char* argv[]) assert (fpv5->external_repositories[0].load () == cr); assert (fpv5->priority == priority::high); - assert (fpv5->priority.comment == "Due to critical bug fix."); + assert (fpv5->priority.comment == + "Critical bug fixes, performance improvement."); const char ch[] = R"DLM(1.2.4-1 * applied patch for critical bug-219 @@ -360,7 +363,7 @@ main (int argc, char* argv[]) assert (fpv5->dependencies.size () == 2); assert (fpv5->dependencies[0].size () == 2); assert (fpv5->dependencies[0].comment == - "Crashes in range [1.1, 2.3.0]."); + "Crashes with 1.1.0-2.3.0."); assert (fpv5->dependencies[0][0] == (dependency { @@ -374,31 +377,31 @@ main (int argc, char* argv[]) brep::optional ( dependency_constraint{comparison::gt, version ("2.3.0")})})); - assert (fpv5->dependencies[1].size () == 1); - assert (fpv5->dependencies[1].comment == "Newer - better."); + assert (fpv5->dependencies[1].size () == 2); + assert (fpv5->dependencies[1].comment == "The newer the better."); - assert (fpv5->dependencies[1][0] == - (dependency {"libstudxml", nullopt})); + assert (fpv5->dependencies[1][0] == (dependency {"libstudxml", nullopt})); + assert (fpv5->dependencies[1][1] == (dependency {"libexpat", nullopt})); requirements& fpvr5 (fpv5->requirements); assert (fpvr5.size () == 4); assert (fpvr5[0] == strings ({"linux", "windows", "macosx"})); assert (!fpvr5[0].conditional); - assert (fpvr5[0].comment == "Symbian is coming."); + assert (fpvr5[0].comment == "Symbian support is coming."); assert (fpvr5[1] == strings ({"c++11"})); assert (!fpvr5[1].conditional); assert (fpvr5[1].comment.empty ()); - assert (fpvr5[2] == strings ({"VC++"})); + assert (fpvr5[2].empty ()); assert (fpvr5[2].conditional); - assert (fpvr5[2].comment == "12.0 or later if targeting Windows."); + assert (fpvr5[2].comment == + "libc++ standard library if using Clang on Mac OS X."); - assert (fpvr5[3].empty ()); + assert (fpvr5[3] == strings ({"vc++ >= 12.0"})); assert (fpvr5[3].conditional); - assert (fpvr5[3].comment == - "libc++ standard library if using Clang on Mac OS X."); + assert (fpvr5[3].comment == "Only if using VC++ on Windows."); // Verify libexp package version. // diff --git a/tests/loader/internal/1/math/packages b/tests/loader/internal/1/math/packages index 98a21d2..d55a9e3 100644 --- a/tests/loader/internal/1/math/packages +++ b/tests/loader/internal/1/math/packages @@ -13,23 +13,29 @@ location: libexp-1+1.2.tar.gz name: libfoo version: 1.2.4-1 summary: The Foo Math Library -description: A modern C++ library with easy to use linear algebra and \ -optimization tools. There are over 100 functions in total with an extensive \ -test suite. The API is similar to MATLAB. +description:\ +A modern C++ library with easy to use linear algebra and lot of optimization +tools. + +There are over 100 functions in total with an extensive test suite. The API is +similar to MATLAB. + +Useful for conversion of research code into production environments. +\ license: LGPLv2, MIT; If using with GNU TLS. license: BSD; If using with OpenSSL. -priority: high; Due to critical bug fix. +priority: high; Critical bug fixes, performance improvement. tags: c++, foo, math url: http://www.example.com/foo/; Project home page. -email: foo-users@example.com; Public mailing list. +email: foo-users@example.com; Public mailing list. Read FAQ before posting. package-url: http://www.example.com/foo/pack; Package details. package-email: pack@example.com; Current packager. -depends: libmisc < 1.1 | libmisc > 2.3.0; Crashes in range [1.1, 2.3.0]. -depends: ? libstudxml; Newer - better. -requires: linux | windows | macosx; Symbian is coming. +depends: libmisc < 1.1 | libmisc > 2.3.0; Crashes with 1.1.0-2.3.0. +depends: ? libstudxml | libexpat; The newer the better. +requires: linux | windows | macosx; Symbian support is coming. requires: c++11 -requires: ? VC++; 12.0 or later if targeting Windows. requires: ? ; libc++ standard library if using Clang on Mac OS X. +requires: ? vc++ >= 12.0; Only if using VC++ on Windows. location: libfoo-1.2.4-1.tar.gz changes:\ 1.2.4-1 diff --git a/tests/web/xhtml/test.out b/tests/web/xhtml/test.out index ffa6df6..a590e1f 100644 --- a/tests/web/xhtml/test.out +++ b/tests/web/xhtml/test.out @@ -2,6 +2,7 @@ + Example XHTML5 document diff --git a/web/apache/request.cxx b/web/apache/request.cxx index 497d2d6..9887104 100644 --- a/web/apache/request.cxx +++ b/web/apache/request.cxx @@ -99,33 +99,31 @@ namespace web { if (::strcasecmp (h->key, "Cookie") == 0) { - for (const char* n (h->val); n != 0; ) + for (const char* n (h->val); n != nullptr; ) { const char* v (strchr (n, '=')); const char* e (strchr (n, ';')); - if (e && e < v) - v = 0; + if (e != nullptr && e < v) + v = nullptr; - string name (v + string name (v != nullptr ? mime_url_decode (n, v, true) : (e ? mime_url_decode (n, e, true) : mime_url_decode (n, n + strlen (n), true))); - string value; + optional value; if (v++) - { value = e ? mime_url_decode (v, e, true) : mime_url_decode (v, v + strlen (v), true); - } - if (!name.empty () || !value.empty ()) + if (!name.empty () || value) cookies_->emplace_back (move (name), move (value)); - n = e ? e + 1 : 0; + n = e ? e + 1 : nullptr; } } } @@ -252,33 +250,31 @@ namespace web void request:: parse_parameters (const char* args) { - for (auto n (args); n != 0; ) + for (auto n (args); n != nullptr; ) { const char* v (strchr (n, '=')); const char* e (strchr (n, '&')); - if (e && e < v) - v = 0; + if (e != nullptr && e < v) + v = nullptr; - string name (v + string name (v != nullptr ? mime_url_decode (n, v) : (e ? mime_url_decode (n, e) : mime_url_decode (n, n + strlen (n)))); - string value; + optional value; if (v++) - { value = e ? mime_url_decode (v, e) : mime_url_decode (v, v + strlen (v)); - } - if (!name.empty () || !value.empty ()) + if (!name.empty () || value) parameters_->emplace_back (move (name), move (value)); - n = e ? e + 1 : 0; + n = e ? e + 1 : nullptr; } } } diff --git a/web/apache/service.cxx b/web/apache/service.cxx index 69bb874..782e09b 100644 --- a/web/apache/service.cxx +++ b/web/apache/service.cxx @@ -70,7 +70,7 @@ namespace web return 0; } - srv.options_.emplace_back (name, value); + srv.options_.emplace_back (name, string (value)); return 0; } diff --git a/web/module b/web/module index 7398d45..25c4bf2 100644 --- a/web/module +++ b/web/module @@ -14,6 +14,7 @@ #include // runtime_error #include +#include namespace web { @@ -57,15 +58,18 @@ namespace web sequence_error (std::string d): std::runtime_error (std::move (d)) {} }; + template + using optional = butl::optional; + struct name_value { // These should eventually become string_view's. // std::string name; - std::string value; + optional value; name_value () {} - name_value (std::string n, std::string v) + name_value (std::string n, optional v) : name (std::move (n)), value (std::move (v)) {} }; diff --git a/web/xhtml b/web/xhtml index b4c1a0d..7446a27 100644 --- a/web/xhtml +++ b/web/xhtml @@ -309,10 +309,16 @@ namespace web static const element H5 ("h5"); static const element H6 ("h6"); static const element LI ("li"); + static const element LINK ("link"); static const element META ("meta"); static const element P ("p"); static const element PRE ("pre"); + static const element TABLE ("table"); + static const element TBODY ("tbody"); + static const element TD ("td"); + static const element TH ("th"); static const element TITLE ("title"); + static const element TR ("tr"); static const element UL ("ul"); static const inline_element A ("a"); @@ -326,14 +332,17 @@ namespace web // Attributes. // - static const attribute CLASS ("class"); - static const attribute CONTENT ("content"); - static const attribute HREF ("href"); - static const attribute ID ("id"); - static const attribute NAME ("name"); - static const attribute STYLE ("style"); - static const attribute TYPE ("type"); - static const attribute VALUE ("value"); + + static const attribute AUTOFOCUS ("autofocus"); + static const attribute CLASS ("class"); + static const attribute CONTENT ("content"); + static const attribute HREF ("href"); + static const attribute ID ("id"); + static const attribute NAME ("name"); + static const attribute REL ("rel"); + static const attribute STYLE ("style"); + static const attribute TYPE ("type"); + static const attribute VALUE ("value"); } } diff --git a/www/common.css b/www/common.css new file mode 100644 index 0000000..f1023df --- /dev/null +++ b/www/common.css @@ -0,0 +1,317 @@ +/* +Nexus 5 360 598 +Nexus 6 412 690 +iPhone 5 320 568 +iPhone 6 375 667 +iPhone 6+ 414 736 +Galaxy 5 360 640 +Galaxy 6 360 640 + +iPad 768 1024 +Galaxy 2,3 10" 800 1280 +Galaxy 2 7" 600 1024 + +320-359 small phone portrait +360-567 big phone portrate +568-1023 phone landscape, tablets portrate +1024- tablets landscape, monitor +*/ + +html +{ + font-family: sans-serif; + font-weight: normal; + font-size: 18px; + line-height: 1.4em; +} + +input +{ + font-family: inherit; + font-weight: inherit; + font-size: inherit; + line-height: inherit; +} + +body +{ + margin: 0; /* There is non-0 default margin on body. */ +} + +/* 320px ~ 20em @ 1 font-size. This is factored in when doing font + boosting. Specifying 320px directly seems to mess it up. + + Font boosting results for font-size:1.1em, min-width:18em on Nexus 5: + + - about 40 characters in portrait + - about 70 characters in landscape + - landscape font appears slightly smaller + + 18em on smaller phones (e.g., iPhone 4) appears to trigger scrolling. + 17em works well, however. +*/ + +body {min-width: 17em;} + +@media only screen and (min-width: 360px) +{ + body {min-width: 19em;} +} + +/* + * Header. + */ +#header +{ + width: 100%; + + background: rgba(0, 0, 0, 0.7); + border-bottom: 1px solid rgba(255, 255, 255, 0.26); /* Off background. */ + + padding: .3em 0 .3em 0; + margin: 0 0 1.4em 0; +} + +#header-menu +{ + /* Same as in #content below. */ + max-width: 40em; + margin: 0 auto 0 auto; + padding: 0 .4em 0 .4em; + + text-align: right; +} + +#header-menu a:nth-child(n + 2) {padding-left: 1.2em;} + +#header-menu a {font-size: 1.12em; color: #eee; text-decoration: none;} +#header-menu a:visited {color: #eee;} +#header-menu a:hover, #header-menu a:active {color: #fff; text-decoration: underline;} + + +/* + * Content. + */ +#content +{ + max-width: 40em; + margin: 0 auto 0 auto; + padding: 0 .4em 0 .4em; /* Space between text and browser frame. */ +} + +/* + * Footer. + */ +#footer +{ + text-align: center; +} + +/* Until we have actual content in the footer. */ +#footer:before {content: "\A0";} +#footer:after {content: "\A0";} + +/* Screen size indicator in the footer. */ +#footer +{ + border-left: 1px solid; + border-right: 1px solid; + margin: 0 1px 0 1px; /* To see the border. */ +} + +@media only screen and (max-width: 359px) +{ + #footer {border-color: red;} +} + +@media only screen and (min-width: 360px) and (max-width: 567px) +{ + #footer {border-color: orange;} +} + +@media only screen and (min-width: 568px) and (max-width: 1023px) +{ + #footer {border-color: blue;} +} + +@media only screen and (min-width: 1024px) +{ + #footer {border-color: green;} +} + +/* + * Common elements. + */ +p {text-align: justify;} + +code, pre +{ + font-size: 0.94em; +} + +a {color: #006fbf; text-decoration: none;} +a:hover, a:active {color: #0087e7; text-decoration: underline;} +a:visited {color: #003388;} + +/* + * Form layout table. + */ +.form-table +{ + width: 100%; + border-collapse: collapse; + margin-top: 1.5em; +} + +.form-table table, .form-table th, .form-table td +{ + border: none; + padding: 0; +} + +/* + * Property list table. + */ +.proplist +{ + /* Extend the table into #content's margins that are used to separate + from the browser frame. If this table gets background/stripe, then + we want it to cover that extra space so that we get a margin between + the text and the background edge. This also means that the background + will touch the browser frame. This looks ok as long as we don't use + fancy things like rounded corners. */ + + width: calc(100% + .8rem); /* Fill the page plus #content margin. */ + padding-right: .4rem; + padding-left: .4rem; + margin-left: -.4rem; + + table-layout: fixed; + + border: none; + border-spacing: 0 0; +} + +.proplist th, .proplist td {padding: .08em 0 .08em 0;} + +.proplist th +{ + font-family: monospace; + font-weight: normal; + text-align: left; +} +.proplist th:after {content: ":";} + +.proplist td .comment +{ + color: #666; + font-size: 0.833em; +} + +/* Flexbox-based left/right aligned value/comment implementation. */ +.proplist td +{ + width: 100%; + white-space: nowrap; + + display: -webkit-inline-flex; + display: inline-flex; + + -webkit-justify-content: space-between; + justify-content: space-between; +} + +.proplist td .value +{ + display: inline-block; + + -webkit-flex-shrink: 1; + flex-shrink: 1; + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.proplist td .comment +{ + display: inline-block; + + margin-left: 1em; + + -webkit-flex-shrink: 100000; + flex-shrink: 100000; + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Fallback for browsers that still don't support flexbox. */ + +.proplist td +{ + text-align: right; + overflow: hidden; + text-overflow: ellipsis; +} + +.proplist td .value +{ + float: left; + text-align: left; +} + +/* Re-styling for full page variant. */ + +.full .proplist td +{ + flex-wrap: wrap; + -webkit-flex-wrap: wrap; + + white-space: normal; +} + +.full .proplist th +{ + vertical-align: top; +} + +.full .proplist td .value +{ + margin-right: 1em; + + white-space: normal; +} + +.full .proplist td .comment +{ + margin-left: 0; + + text-align: left; + white-space: normal; +} + +/* + * Pager. + */ +#pager +{ + margin-top: 1.8em; + + font-size: 0.916em; + text-align: center; +} + +#pager a +{ + padding: 0 0.4em 0 0.4em; +} + +#pager #prev:before {content: "<\A0";} + +#pager #curr {font-weight: bold;} +#pager #curr:before {content: "["; font-weight: normal;} +#pager #curr:after {content: "]"; font-weight: normal;} + +#pager #next:after {content: "\A0>";} diff --git a/www/package-details.css b/www/package-details.css new file mode 100644 index 0000000..941f97e --- /dev/null +++ b/www/package-details.css @@ -0,0 +1,107 @@ +#heading +{ + display: table; + table-layout: fixed; + width: 100%; + border: none; +} + +/* Since it is a link to itself, it will always be visited. */ +#heading a:visited {color: #006fbf;} +#heading a:hover, h1 a:active {color: #0087e7; text-decoration: none;} + +h1 +{ + font-family: monospace; + font-weight: normal; + font-size: 2.074em; + line-height: 1.4em; + + margin: .6em 0 .6em 0; + + display: table-cell; + text-align: left; +} + +#heading > a +{ + font-size: 1.32em; + line-height: 1.4em; + + display: table-cell; + text-align: right; + width: 3.2em; + vertical-align: middle; +} + +h2 +{ + font-style: italic; + font-weight: normal; + font-size: 1.32em; + line-height: 1.4em; + + margin: .4em 0 .4em 0; +} + +h1, h2 +{ + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* + * Package details table. + */ +#package +{ + margin-top: 1.5em; + margin-bottom: 1.5em; +} +#package th {width: 6.4em;} + +/* + * Search form (based on form-table) + */ +#search-txt, #search-txt input {width: 100%;} +#search-btn {padding-left: .4em;} + +/* + * Version count. + */ +#count +{ + font-size: 1.32em; + line-height: 1.4em; + color: #555; + + margin: 1.2em 0 0 0; +} + +/* + * Version table. + */ +table.version +{ + margin-top: .8em; + margin-bottom: .8em; + + padding-top: .4em; + padding-bottom: .4em; + +} +table.version:nth-child(even) {background-color: rgba(0, 0, 0, 0.07);} + +table.version th {width: 6.4em;} + +table.version tr.version td .value, +table.version tr.priority td .value, +table.version tr.location td .value, +table.version tr.depends td .value, +table.version tr.requires td .value +{ + /* style. */ + font-family: monospace; + font-size: 0.94em; +} diff --git a/www/package-search.css b/www/package-search.css new file mode 100644 index 0000000..ef8b59c --- /dev/null +++ b/www/package-search.css @@ -0,0 +1,41 @@ +/* + * Search form (based on form-table) + */ +#search-txt, #search-txt input {width: 100%;} +#search-btn {padding-left: .4em;} + +/* + * Package count. + */ +#count +{ + font-size: 1.32em; + line-height: 1.4em; + color: #555; + + margin: 1.2em 0 0 0; +} + +/* + * Version table. + */ +.package +{ + margin-top: .8em; + margin-bottom: .8em; + + padding-top: .4em; + padding-bottom: .4em; +} +.package:nth-child(even) {background-color: rgba(0, 0, 0, 0.07);} + +.package th {width: 6.4em;} + +.package tr.name td .value, +.package tr.depends td .value, +.package tr.requires td .value +{ + /* style. */ + font-family: monospace; + font-size: 0.94em; +} diff --git a/www/package-version-details.css b/www/package-version-details.css new file mode 100644 index 0000000..a701a5e --- /dev/null +++ b/www/package-version-details.css @@ -0,0 +1,161 @@ +#heading +{ + display: table; + table-layout: fixed; + width: 100%; + border: none; +} + +/* Since it is a link to itself, it will always be visited. */ +#heading a:visited {color: #006fbf;} +#heading a:hover, h1 a:active {color: #0087e7; text-decoration: none;} + +h1 +{ + font-family: monospace; + font-weight: normal; + font-size: 2.074em; + line-height: 1.4em; + color: #444; + + margin: .6em 0 .6em 0; + + display: table-cell; + text-align: left; +} + +#heading > a +{ + font-size: 1.32em; + line-height: 1.4em; + + display: table-cell; + text-align: right; + width: 3.2em; + vertical-align: middle; +} + +h1 a:first-child {margin-right: .14em;} +h1 a:last-child {margin-left: .14em;} + +h2 +{ + font-style: italic; + font-weight: normal; + font-size: 1.32em; + line-height: 1.4em; + + margin: .4em 0 .4em 0; +} + +h3 +{ + font-family: monospace; + font-weight: normal; + font-size: 1.26em; + line-height: 1.4em; + + margin: 1.8em 0 0 0; +} +h3:after{content: ":";} + +h1, h2, h3 +{ + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* + * Version details table. + */ +#version +{ + margin-top: .5em; + margin-bottom: 1em; + + padding-top: .4em; + padding-bottom: .4em; + + background-color: rgba(0, 0, 0, 0.07); /* Emphasize. */ + + /* + background: #eee; + border-radius: 6px; + border: 1px outset rgba(230,230,230,.24); + */ +} +#version th {width: 6.4em;} + +#version tr.version td .value, +#version tr.priority td .value, +#version tr.location td .value +{ + /* style. */ + font-family: monospace; + font-size: 0.94em; +} + +/* + * Package details table. + */ +#package +{ + margin-top: 1.2em; + margin-bottom: 1em; +} +#package th {width: 6.4em;} + +/* + * Dependencies and requirements tables. + */ +#depends {margin-top: .4em; margin-bottom: 1em;} +#depends th {width: 2.8em; text-align: center;} +#depends th:after{content: "";} + +/* Striping. For some reason (related to flexbox), this works exactly as + we want, that is, the background extends all the way to the browser's + right frame. */ +#depends tr:nth-child(even) td {background-color: rgba(0, 0, 0, 0.07);} +#depends td {padding-left: .4em;} +#depends td .comment {padding-right: .4em;} + +/* +#depends td .value {padding-left: .4em;} +#depends td .comment {padding-right: .4em;} +*/ + +#depends tr.depends td .value +{ + /* style. */ + font-family: monospace; + font-size: 0.94em; +} + +#requires {margin-top: .4em; margin-bottom: 1em;} +#requires th {width: 2.8em; text-align: center;} +#requires th:after{content: "";} + +#requires tr:nth-child(even) td {background-color: rgba(0, 0, 0, 0.07);} +#requires td {padding-left: .4em;} +#requires td .comment {padding-right: .4em;} + +#requires tr.requires td .value +{ + /* style. */ + font-family: monospace; + font-size: 0.94em; +} + +/* + * Changes. + * + * This is a
 block that fits lines up to 80 characters long and
+ * wraps longer ones.
+ */
+#changes 
+{     
+  font-size: 0.78em;
+  white-space: pre-wrap;
+  margin: .5em 0 .5em 0;
+}
-- 
cgit v1.1