From 4fec7e73201ed50e4a4157cb1ea1f1c63566dd89 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Sun, 28 Feb 2016 16:49:17 +0200 Subject: Menu customization --- brep/buildfile | 1 + brep/mod-package-details.cxx | 5 +- brep/mod-package-search.cxx | 5 +- brep/mod-package-version-details.cxx | 5 +- brep/mod-repository-details.cxx | 5 +- brep/options-types | 10 +++ brep/options.cli | 31 ++++++++-- brep/page | 52 +++++++++------- brep/page.cxx | 41 +++++++++---- brep/types-parsers | 16 +++++ brep/types-parsers.cxx | 51 ++++++++++++++++ etc/brep-module.conf | 14 +++++ web/xhtml-fragment | 48 +++++++++++++++ web/xhtml-fragment.cxx | 115 +++++++++++++++++++++++++++++++++++ 14 files changed, 357 insertions(+), 42 deletions(-) create mode 100644 web/xhtml-fragment create mode 100644 web/xhtml-fragment.cxx diff --git a/brep/buildfile b/brep/buildfile index b77952c..360f82d 100644 --- a/brep/buildfile +++ b/brep/buildfile @@ -58,6 +58,7 @@ src = \ ../web/{hxx cxx}{ mime-url-encoding } \ ../web/{hxx }{ module } \ ../web/{hxx }{ xhtml } \ + ../web/{hxx cxx}{ xhtml-fragment } \ ../web/apache/{hxx }{ log } \ ../web/apache/{hxx ixx cxx}{ request } \ ../web/apache/{hxx txx cxx}{ service } \ diff --git a/brep/mod-package-details.cxx b/brep/mod-package-details.cxx index 98b2135..fa07dd1 100644 --- a/brep/mod-package-details.cxx +++ b/brep/mod-package-details.cxx @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -65,6 +66,8 @@ handle (request& rq, response& rs) // static const size_t res_page (options_->search_results ()); static const dir_path& root (options_->root ()); + static const fragment& logo (options_->logo ()); + static const vector& menu (options_->menu ()); const string& name (*rq.path ().rbegin ()); const string ename (mime_url_encode (name)); @@ -127,7 +130,7 @@ handle (request& rq, response& rs) << SCRIPT << " " << ~SCRIPT << ~HEAD << BODY - << DIV_HEADER (root) + << DIV_HEADER (root, logo, menu) << DIV(ID="content"); if (full) diff --git a/brep/mod-package-search.cxx b/brep/mod-package-search.cxx index 5725da0..7505e02 100644 --- a/brep/mod-package-search.cxx +++ b/brep/mod-package-search.cxx @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -78,6 +79,8 @@ handle (request& rq, response& rs) // static const size_t res_page (options_->search_results ()); static const dir_path& root (options_->root ()); + static const fragment& logo (options_->logo ()); + static const vector& menu (options_->menu ()); params::package_search params; @@ -121,7 +124,7 @@ handle (request& rq, response& rs) << SCRIPT << " " << ~SCRIPT << ~HEAD << BODY - << DIV_HEADER (root) + << DIV_HEADER (root, logo, menu) << DIV(ID="content"); session sn; diff --git a/brep/mod-package-version-details.cxx b/brep/mod-package-version-details.cxx index 5090a19..c9055a5 100644 --- a/brep/mod-package-version-details.cxx +++ b/brep/mod-package-version-details.cxx @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -51,6 +52,8 @@ handle (request& rq, response& rs) // server process. // static const dir_path& root (options_->root ()); + static const fragment& logo (options_->logo ()); + static const vector& menu (options_->menu ()); auto i (rq.path ().rbegin ()); version ver; @@ -104,7 +107,7 @@ handle (request& rq, response& rs) << CSS_LINKS (path ("package-version-details.css"), root) << ~HEAD << BODY - << DIV_HEADER (root) + << DIV_HEADER (root, logo, menu) << DIV(ID="content"); if (full) diff --git a/brep/mod-repository-details.cxx b/brep/mod-repository-details.cxx index 742bee9..0a88a16 100644 --- a/brep/mod-repository-details.cxx +++ b/brep/mod-repository-details.cxx @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -57,6 +58,8 @@ handle (request& rq, response& rs) // server process. // static const dir_path& root (options_->root ()); + static const fragment& logo (options_->logo ()); + static const vector& menu (options_->menu ()); // Make sure no parameters passed. // @@ -79,7 +82,7 @@ handle (request& rq, response& rs) << CSS_LINKS (path ("repository-details.css"), root) << ~HEAD << BODY - << DIV_HEADER (root) + << DIV_HEADER (root, logo, menu) << DIV(ID="content"); transaction t (db_->begin ()); diff --git a/brep/options-types b/brep/options-types index 767423c..922637b 100644 --- a/brep/options-types +++ b/brep/options-types @@ -17,6 +17,16 @@ namespace brep full, brief }; + + struct page_menu + { + string label; + string link; + + page_menu () = default; + page_menu (string b, string l): label (move (b)), link (move (l)) {} + }; + } #endif // BREP_OPTIONS_TYPES diff --git a/brep/options.cli b/brep/options.cli index 2da6718..986432a 100644 --- a/brep/options.cli +++ b/brep/options.cli @@ -2,6 +2,8 @@ // copyright : Copyright (c) 2014-2016 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +include ; + include ; include ; @@ -70,6 +72,27 @@ namespace brep } }; + class page + { + web::xhtml::fragment logo + { + "", + "Web page logo. It is displayed in the page header aligned to the left + edge. The value is treated as an XHTML5 fragment." + } + + vector menu; + { + "", + "Web page menu. Each entry is displayed in the page header in the order + specified and aligned to the right edge. A link target that starts + with \cb{/} or contains \cb{:} is used as is. Otherwise, it is prefixed + with the repository web interface root." + + // @@ Might need updating. + } + }; + class search { uint16_t search-results = 10 @@ -104,19 +127,19 @@ namespace brep // Module options. // - class package_search: search, db, module + class package_search: search, db, page, module { }; - class package_details: package, search, db, module + class package_details: package, search, db, page, module { }; - class package_version_details: package, db, module + class package_version_details: package, db, page, module { }; - class repository_details: db, module + class repository_details: db, page, module { }; diff --git a/brep/page b/brep/page index 5836ea6..4f97ef8 100644 --- a/brep/page +++ b/brep/page @@ -7,10 +7,13 @@ #include +#include + #include #include #include +#include // page_menu namespace brep { @@ -25,7 +28,7 @@ namespace brep CSS_LINKS (const path& p, const dir_path& r): path_ (p), root_ (r) {} void - operator() (xml::serializer& s) const; + operator() (xml::serializer&) const; private: const path& path_; @@ -37,13 +40,18 @@ namespace brep class DIV_HEADER { public: - DIV_HEADER (const dir_path& r): root_ (r) {} + DIV_HEADER (const dir_path& root, + const web::xhtml::fragment& logo, + const vector& menu): + root_ (root), logo_ (logo), menu_ (menu) {} void - operator() (xml::serializer& s) const; + operator() (xml::serializer&) const; private: const dir_path& root_; + const web::xhtml::fragment& logo_; + const vector& menu_; }; // Generates package search form element. @@ -54,7 +62,7 @@ namespace brep FORM_SEARCH (const string& q): query_ (q) {} void - operator() (xml::serializer& s) const; + operator() (xml::serializer&) const; private: const string& query_; @@ -73,7 +81,7 @@ namespace brep : count_ (c), singular_ (s), plural_ (p) {} void - operator() (xml::serializer& s) const; + operator() (xml::serializer&) const; private: size_t count_; @@ -90,7 +98,7 @@ namespace brep : name_ (n), query_param_ (q), root_ (r) {} void - operator() (xml::serializer& s) const; + operator() (xml::serializer&) const; private: const string& name_; @@ -114,7 +122,7 @@ namespace brep : package_ (nullptr), version_ (v), root_ (nullptr) {} void - operator() (xml::serializer& s) const; + operator() (xml::serializer&) const; private: const string* package_; @@ -130,7 +138,7 @@ namespace brep TR_SUMMARY (const string& s): summary_ (s) {} void - operator() (xml::serializer& s) const; + operator() (xml::serializer&) const; private: const string& summary_; @@ -144,7 +152,7 @@ namespace brep TR_LICENSE (const license_alternatives& l): licenses_ (l) {} void - operator() (xml::serializer& s) const; + operator() (xml::serializer&) const; private: const license_alternatives& licenses_; @@ -159,7 +167,7 @@ namespace brep TR_LICENSES (const license_alternatives& l): licenses_ (l) {} void - operator() (xml::serializer& s) const; + operator() (xml::serializer&) const; private: const license_alternatives& licenses_; @@ -173,7 +181,7 @@ namespace brep TR_TAGS (const strings& ts, const dir_path& r): tags_ (ts), root_ (r) {} void - operator() (xml::serializer& s) const; + operator() (xml::serializer&) const; private: const strings& tags_; @@ -189,7 +197,7 @@ namespace brep : dependencies_ (d), root_ (r) {} void - operator() (xml::serializer& s) const; + operator() (xml::serializer&) const; private: const dependencies& dependencies_; @@ -204,7 +212,7 @@ namespace brep TR_REQUIRES (const requirements& r): requirements_ (r) {} void - operator() (xml::serializer& s) const; + operator() (xml::serializer&) const; private: const requirements& requirements_; @@ -218,7 +226,7 @@ namespace brep TR_URL (const url& u, const char* l = "url"): url_ (u), label_ (l) {} void - operator() (xml::serializer& s) const; + operator() (xml::serializer&) const; private: const url& url_; @@ -234,7 +242,7 @@ namespace brep : email_ (e), label_ (l) {} void - operator() (xml::serializer& s) const; + operator() (xml::serializer&) const; private: const email& email_; @@ -249,7 +257,7 @@ namespace brep TR_PRIORITY (const priority& p): priority_ (p) {} void - operator() (xml::serializer& s) const; + operator() (xml::serializer&) const; private: const priority& priority_; @@ -264,7 +272,7 @@ namespace brep : name_ (n), root_ (r) {} void - operator() (xml::serializer& s) const; + operator() (xml::serializer&) const; private: const string& name_; @@ -279,7 +287,7 @@ namespace brep TR_DOWNLOAD (const string& u): url_ (u) {} void - operator() (xml::serializer& s) const; + operator() (xml::serializer&) const; private: const string& url_; @@ -293,7 +301,7 @@ namespace brep SPAN_COMMENT (const string& c): comment_ (c) {} void - operator() (xml::serializer& s) const; + operator() (xml::serializer&) const; private: const string& comment_; @@ -315,7 +323,7 @@ namespace brep : description_ (d), length_ (l), url_ (&u) {} void - operator() (xml::serializer& s) const; + operator() (xml::serializer&) const; private: const string& description_; @@ -340,7 +348,7 @@ namespace brep : changes_ (c), length_ (l), url_ (&u) {} void - operator() (xml::serializer& s) const; + operator() (xml::serializer&) const; private: const string& changes_; @@ -360,7 +368,7 @@ namespace brep const string& url); void - operator() (xml::serializer& s) const; + operator() (xml::serializer&) const; private: size_t current_page_; diff --git a/brep/page.cxx b/brep/page.cxx index a6c7da2..27e1b2a 100644 --- a/brep/page.cxx +++ b/brep/page.cxx @@ -40,18 +40,35 @@ namespace brep void DIV_HEADER:: operator() (serializer& s) const { - s << DIV(ID="header-bar") - << DIV(ID="header") - << DIV(ID="header-logo") - << ~DIV - << DIV(ID="header-menu") - << DIV(ID="header-menu-body") - << A(HREF=root_) << "Packages" << ~A - << A(HREF=root_.string () + "?about") << "About" << ~A - << ~DIV - << ~DIV - << ~DIV - << ~DIV; + if (!logo_.empty () || !menu_.empty ()) + { + s << DIV(ID="header-bar") + << DIV(ID="header"); + + if (!logo_.empty ()) + s << DIV(ID="header-logo") << logo_ << ~DIV; + + if (!menu_.empty ()) + { + s << DIV(ID="header-menu") + << DIV(ID="header-menu-body"); + + for (const auto& m: menu_) + { + const string& l (m.link[0] == '/' || m.link.find (':') != string::npos + ? m.link + : root_.string () + m.link); + + s << A(HREF=l) << m.label << ~A; + } + + s << ~DIV + << ~DIV; + } + + s << ~DIV + << ~DIV; + } } // FORM_SEARCH diff --git a/brep/types-parsers b/brep/types-parsers index 8401237..78b7088 100644 --- a/brep/types-parsers +++ b/brep/types-parsers @@ -8,6 +8,8 @@ #ifndef BREP_TYPES_PARSERS #define BREP_TYPES_PARSERS +#include + #include #include @@ -35,6 +37,20 @@ namespace brep static void parse (page_form&, scanner&); }; + + template <> + struct parser + { + static void + parse (page_menu&, scanner&); + }; + + template <> + struct parser + { + static void + parse (web::xhtml::fragment&, scanner&); + }; } } diff --git a/brep/types-parsers.cxx b/brep/types-parsers.cxx index d46ea18..67f4812 100644 --- a/brep/types-parsers.cxx +++ b/brep/types-parsers.cxx @@ -7,6 +7,7 @@ #include using namespace std; +using namespace web::xhtml; namespace brep { @@ -59,5 +60,55 @@ namespace brep else throw invalid_value (o, v); } + + // Parse page_menu. + // + void parser:: + parse (page_menu& x, scanner& s) + { + const char* o (s.next ()); + + if (!s.more ()) + throw missing_value (o); + + const string v (s.next ()); + + auto p (v.find ('=')); + if (p != string::npos) + { + string label (v, 0, p); + string link (v, p + 1); + + if (!label.empty ()) + { + x = page_menu (move (label), move (link)); + return; + } + } + + throw invalid_value (o, v); + } + + // Parse web::xhtml::fragment. + // + void parser:: + parse (fragment& x, scanner& s) + { + const char* o (s.next ()); + + if (!s.more ()) + throw missing_value (o); + + const char* v (s.next ()); + + try + { + x = fragment (v, o); + } + catch (const xml::parsing&) + { + throw invalid_value (o, v); + } + } } } diff --git a/etc/brep-module.conf b/etc/brep-module.conf index 26767d6..8525135 100644 --- a/etc/brep-module.conf +++ b/etc/brep-module.conf @@ -4,6 +4,20 @@ # out options indicate their default values. # +# Web page logo. It is displayed in the page header aligned to the left edge. +# The value is treated as an XHTML5 fragment. +# +# logo "" + +# Web page menu. Each entry is displayed in the page header in the order +# specified and aligned to the right edge. A link target that starts with '/' or +# contains ':' is used as is. Otherwise, it is prefixed with the repository web +# interface root. +# +menu Packages= +menu About=?about + + # Number of results per page. # # search-results 10 diff --git a/web/xhtml-fragment b/web/xhtml-fragment new file mode 100644 index 0000000..a0964b0 --- /dev/null +++ b/web/xhtml-fragment @@ -0,0 +1,48 @@ +// file : web/xhtml-fragment -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef WEB_XHTML_FRAGMENT +#define WEB_XHTML_FRAGMENT + +#include +#include +#include // pair + +#include +#include + +namespace web +{ + namespace xhtml + { + // A parsed (via xml::parser) XHTML fragment that can later be serialized + // to xml::serializer. + // + class fragment + { + public: + fragment () = default; + + // Parse string as XHTML document fragment. The fragment should be + // complete, in the sense that all elements should have closing tags. + // Elements and attributes are considered to be in the namespace of the + // entire XHTML document, so no namespace should be specified for them. + // Do not validate against XHTML vocabulary. Can throw xml::parsing + // exception. + // + fragment (const std::string& xhtml, const std::string& input_name); + + void + operator() (xml::serializer&) const; + + bool + empty () const {return events_.empty ();} + + private: + std::vector> events_; + }; + } +} + +#endif // WEB_XHTML_FRAGMENT diff --git a/web/xhtml-fragment.cxx b/web/xhtml-fragment.cxx new file mode 100644 index 0000000..34247dd --- /dev/null +++ b/web/xhtml-fragment.cxx @@ -0,0 +1,115 @@ +// file : web/xhtml-fragment.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include + +#include +#include + +#include + +using namespace std; +using namespace xml; + +namespace web +{ + namespace xhtml + { + fragment:: + fragment (const string& text, const string& name) + { + // To parse the fragment make it a valid xml document, wrapping with the + // root element. + // + string doc ("" + text + ""); + + parser p ( + doc.c_str (), + doc.size (), + name, + parser::receive_elements | parser::receive_characters | + parser::receive_attributes_event); + + for (parser::event_type e: p) + { + switch (e) + { + case parser::start_element: + case parser::start_attribute: + { + const auto& n (p.qname ()); + + if (!n.namespace_ ().empty ()) + throw parsing ( + name, p.line (), p.column (), "namespace is not allowed"); + + events_.emplace_back (e, n.name ()); + break; + } + case parser::end_element: + case parser::end_attribute: + { + events_.emplace_back (e, ""); + break; + } + case parser::characters: + { + events_.emplace_back (e, p.value ()); + break; + } + default: + assert (false); + } + } + + // Unwrap the fragment removing the root element events. + // + assert (events_.size () >= 2); + events_.erase (events_.begin ()); + events_.pop_back (); + } + + void fragment:: + operator() (serializer& s) const + { + for (const auto& e: events_) + { + switch (e.first) + { + case parser::start_element: + { + s.start_element (xmlns, e.second); + break; + } + + case parser::start_attribute: + { + s.start_attribute (e.second); + break; + } + case parser::end_element: + { + s.end_element (); + break; + } + case parser::end_attribute: + { + s.end_attribute (); + break; + } + case parser::characters: + { + s.characters (e.second); + break; + } + default: + assert (false); + } + } + } + } +} -- cgit v1.1