From bc4c691d26bbb4dfc7d656521b745dee13eefbaa Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Fri, 4 Nov 2022 20:58:20 +0100 Subject: [PATCH 1/4] Updated Trac to 1.4 (no CSS) This commit is enough to get the site up and running but doesn't contain any CSS fixes. --- DjangoPlugin/tracdjangoplugin/__init__.py | 15 +- README.rst | 2 +- requirements.txt | 8 +- trac-env/templates/custom_ticket.html | 93 ++++++------ trac-env/templates/django_theme.html | 126 ++++++++++++++++ trac-env/templates/site.html | 176 ---------------------- trac-env/templates/site_footer.html | 60 ++++++++ trac-env/templates/site_head.html | 15 ++ trac-env/templates/site_header.html | 21 +++ 9 files changed, 284 insertions(+), 232 deletions(-) create mode 100644 trac-env/templates/django_theme.html delete mode 100644 trac-env/templates/site.html create mode 100644 trac-env/templates/site_footer.html create mode 100644 trac-env/templates/site_head.html create mode 100644 trac-env/templates/site_header.html diff --git a/DjangoPlugin/tracdjangoplugin/__init__.py b/DjangoPlugin/tracdjangoplugin/__init__.py index 3d461df..52a08f3 100644 --- a/DjangoPlugin/tracdjangoplugin/__init__.py +++ b/DjangoPlugin/tracdjangoplugin/__init__.py @@ -7,6 +7,17 @@ from tracext.github import GitHubBrowser +class CustomTheme(Component): + implements(IRequestFilter) + + def pre_process_request(self, req, handler): + return handler + + def post_process_request(self, req, template, data, metadata): + req.chrome["theme"] = "django_theme.html" + return template, data, metadata + + class CustomWikiModule(WikiModule): """Works in combination with the CustomNavigationBar and replaces the default wiki module. Has a different logic for active item @@ -37,7 +48,7 @@ def process_request(self, req): def pre_process_request(self, req, handler): return handler - def post_process_request(self, req, template, data, content_type): + def post_process_request(self, req, template, data, metadata): if data is None: data = {} if req.path_info == "/newticket" and not data.get("preview_mode", False): @@ -48,7 +59,7 @@ def post_process_request(self, req, template, data, content_type): ] data["simple_interface"] = simple_interface template = "custom_ticket.html" - return template, data, content_type + return template, data, metadata class CustomNavigationBar(Component): diff --git a/README.rst b/README.rst index feb0452..dac5525 100644 --- a/README.rst +++ b/README.rst @@ -11,7 +11,7 @@ that can help: the database creation). * Use ``trac-admin ./trac-env/ permission add anonymous TRAC_ADMIN`` to give all permissions to the anonymous user. -* Use the command ``tracd --port 9000 -s trac-env`` to serve Trac locally. +* Use the command ``DJANGO_SETTINGS_MODULE=tracdjangoplugin.settings TRAC_ENV=`pwd`/trac-env gunicorn tracdjangoplugin.wsgi:application --bind 0.0.0.0:9000 --workers=1 --reload`` to serve Trac locally. * If you've modified the ``trackhack.scss`` file, use ``sassc scss/trachacks.scss trac-env/htdocs/css/trachacks.css -s compressed`` to compile it to CSS. diff --git a/requirements.txt b/requirements.txt index dd85e34..4dfd0ed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -Trac==1.2.6 +Trac==1.4.4 Pygments==2.5.2 dnspython==1.15 spambayes == 1.1b1 @@ -6,10 +6,12 @@ psycopg2==2.7.6.1 --no-binary=psycopg2 docutils==0.14 Django==1.11.29 libsass==0.17.0 +Genshi==0.7.7 # still required by some plugins # Trac plugins TracSpamFilter==1.2.7 -TracXMLRPC==1.1.9 +# TracXMLRPC from PyPI does not (yet) have a 1.2.0 release (compatible with Trac 1.4) +https://trac-hacks.org/browser/xmlrpcplugin/trunk?rev=18591&format=zip oauthlib==2.1.0 requests==2.20.1 @@ -19,4 +21,4 @@ trac-github==2.3 gunicorn==19.10.0 sentry-sdk==1.11.0 --e ./DjangoPlugin +-e ./DjangoPlugin \ No newline at end of file diff --git a/trac-env/templates/custom_ticket.html b/trac-env/templates/custom_ticket.html index aa08c14..5c0afef 100644 --- a/trac-env/templates/custom_ticket.html +++ b/trac-env/templates/custom_ticket.html @@ -1,50 +1,43 @@ - - - ${select('*|comment()|text()')} - - -
-

Please read this first:

- -
- ${select('*|text()')} - - - +# extends 'ticket.html' + +# block content +
+

Please read this first:

+ +
+${ super() } +# endblock content \ No newline at end of file diff --git a/trac-env/templates/django_theme.html b/trac-env/templates/django_theme.html new file mode 100644 index 0000000..fb1292f --- /dev/null +++ b/trac-env/templates/django_theme.html @@ -0,0 +1,126 @@ + + {# jinjacheck: "attribute lang" OK #} + +# macro navigation(category) + +# endmacro + +# macro main() +
+ ${navigation('metanav')} + ${navigation('mainnav')} +
+ + # if req.authname != 'anonymous' and not req.session.email: +
+ Add an email address on the Preferences + page to receive updates on tickets you own, have created, or been CCed on. +
+ # endif + + # if resourcepath_template: + # include resourcepath_template ignore missing + # endif + + + ${jmacros.warnings(chrome.warnings)} + + ${jmacros.notices(chrome.notices)} + + # block content + # endblock content +
+ Back to Top +
+# endmacro + + + # block head + # endblock head + + +{# we don't use the modernizer js lib anymore, but the css still uses some classes from it #} + + # block body + + # include 'site_header.html' ignore missing + + + # else +
+ ${main()} +
+ # endif + + # include 'site_footer.html' ignore missing + + # endblock body + + + diff --git a/trac-env/templates/site.html b/trac-env/templates/site.html deleted file mode 100644 index 80155c9..0000000 --- a/trac-env/templates/site.html +++ /dev/null @@ -1,176 +0,0 @@ - - - ${select('*|comment()|text()')} - - - - - - - - - - - - - - - - - - -
-
-

Issues

-
-
- - - - - -
-
- -
- Add an email address on the Preferences - page to receive updates on tickets you own, have created, or been CCed on. -
-
- ${select('./div[@id="banner"]/div[@id="metanav"]')} - ${select('./div[@id="mainnav"]')} - ${select('./div[@id="main"]')} - Back to Top -
-
-
- -
-
- -
- - -
- - - - - diff --git a/trac-env/templates/site_footer.html b/trac-env/templates/site_footer.html new file mode 100644 index 0000000..46b86cc --- /dev/null +++ b/trac-env/templates/site_footer.html @@ -0,0 +1,60 @@ +
+
+ +
+ + +
+ + diff --git a/trac-env/templates/site_head.html b/trac-env/templates/site_head.html new file mode 100644 index 0000000..356cff3 --- /dev/null +++ b/trac-env/templates/site_head.html @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/trac-env/templates/site_header.html b/trac-env/templates/site_header.html new file mode 100644 index 0000000..74c1962 --- /dev/null +++ b/trac-env/templates/site_header.html @@ -0,0 +1,21 @@ + \ No newline at end of file From 8eb9e868b0516629fb249f18cc1c50d7c44b5e3a Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Fri, 4 Nov 2022 22:28:34 +0100 Subject: [PATCH 2/4] Fixed path to favicon in config --- trac-env/conf/trac.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trac-env/conf/trac.ini b/trac-env/conf/trac.ini index 4da6260..9f3e6b3 100644 --- a/trac-env/conf/trac.ini +++ b/trac-env/conf/trac.ini @@ -134,7 +134,7 @@ always_notify_reporter = TicketReporterSubscriber [project] descr = The Web framework for perfectionists with deadlines. footer = -icon = /favicon.ico +icon = site/favicon.ico name = Django url = https://code.djangoproject.com/ From 0ed2f9e5fe86b84ec4ff9aaef068c8409278193d Mon Sep 17 00:00:00 2001 From: Baptiste Mispelon Date: Sat, 13 Jan 2024 23:00:55 +0100 Subject: [PATCH 3/4] Updated box-shadow scss reset Also introduced a script that automatically generates the _noshadows.scss file to make it easier to update Trac. --- noshadows.py | 226 +++++++++++++++++++++++++++++++++++++++++++ scss/_noshadows.scss | 179 ++++++++++++++-------------------- 2 files changed, 296 insertions(+), 109 deletions(-) create mode 100644 noshadows.py diff --git a/noshadows.py b/noshadows.py new file mode 100644 index 0000000..524e02c --- /dev/null +++ b/noshadows.py @@ -0,0 +1,226 @@ +import argparse +from datetime import datetime +from functools import partial, singledispatch +from pathlib import Path +import sys +import unittest + +try: + from tinycss2 import parse_stylesheet + from tinycss2 import ast as tokens +except ImportError: + print("Missing requirement: tinycss2", file=sys.stderr) + sys.exit(1) + + +def get_parser(): + parser = argparse.ArgumentParser( + description="Scan trac's CSS files to detect box-shadow rules and generate a stylesheet that resets them." + ) + parser.add_argument("cssfiles", nargs="*", type=Path, help="The CSS files to scan") + parser.add_argument( + "--outfile", + "-o", + type=argparse.FileType("w"), + default="-", + help="Where to write the output", + ) + parser.add_argument("--tests", action="store_true") + return parser + + +def tripletwise(iterable): # no relation to Jeff + """ + Like itertools.pairwise, but for triplets instead of pairs. + """ + i = iter(iterable) + try: + x, y, z = next(i), next(i), next(i) + except StopIteration: + return + + yield x, y, z + for el in i: + x, y, z = y, z, el + yield x, y, z + + +def skip_whitespace(nodes): + return filter(lambda node: node.type != "whitespace", nodes) + + +def has_shadow(rule): + if not rule.content: + return False + + for a, b, c in tripletwise(skip_whitespace(rule.content)): + if isinstance(a, tokens.IdentToken) and a.value == "box-shadow": + assert isinstance( + c, (tokens.IdentToken, tokens.DimensionToken, tokens.NumberToken) + ), f"Unexpected node type {c.type}" + return isinstance(c, (tokens.DimensionToken, tokens.NumberToken)) + + +def find_shadow(rules): + for rule in rules: + if has_shadow(rule): + yield rule + + +@singledispatch +def tokenstr(token: tokens.Node): + return token.value + + +@tokenstr.register +def _(token: tokens.WhitespaceToken): + return " " + + +@tokenstr.register +def _(token: tokens.SquareBracketsBlock): + return "[" + tokenlist_to_str(token.content) + "]" + + +@tokenstr.register +def _(token: tokens.HashToken): + return f"#{token.value}" + + +@tokenstr.register +def _(token: tokens.StringToken): + return f'"{token.value}"' + + +def tokenlist_to_str(tokens): + return "".join(map(tokenstr, tokens)) + + +def selector_str(rule): + """ + Return the given rule's selector as a string + """ + return tokenlist_to_str(rule.prelude).strip() + + +def reset_css_str(selector): + return f"{selector}{{\n @include noshadow;\n}}" + + +class NoShadowTestCase(unittest.TestCase): + @classmethod + def run_and_exit(cls): + """ + Run all tests on the class and exit with the proper exit status (1 if any failures occured, + 0 otherwise) + """ + runner = unittest.TextTestRunner() + suite = unittest.defaultTestLoader.loadTestsFromTestCase(cls) + result = runner.run(suite) + retval = 0 if result.wasSuccessful() else 1 + sys.exit(retval) + + def test_tripletwise(self): + self.assertEqual( + list(tripletwise("ABCDEF")), + [("A", "B", "C"), ("B", "C", "D"), ("C", "D", "E"), ("D", "E", "F")], + ) + + def test_tripletwise_too_short(self): + self.assertEqual(list(tripletwise("AB")), []) + + def test_skip_whitespace(self): + rules = parse_stylesheet("html { color: red ; }") + self.assertEqual(len(rules), 1) + non_whitespace_content = list(skip_whitespace(rules[0].content)) + self.assertEqual( + len(non_whitespace_content), 4 + ) # attr, colon, value, semicolon + + def test_has_shadow(self): + (rule,) = parse_stylesheet("html {box-shadow: 10px 5px 5px red;}") + self.assertTrue(has_shadow(rule)) + + def test_has_shadow_with_box_shadow_none(self): + (rule,) = parse_stylesheet("html {box-shadow: none;}") + self.assertFalse(has_shadow(rule)) + + def test_has_shadow_empty_rule(self): + (rule,) = parse_stylesheet("html {}") + self.assertFalse(has_shadow(rule)) + + def test_selector_str_tag(self): + (rule,) = parse_stylesheet("html {}") + self.assertEqual(selector_str(rule), "html") + + def test_selector_str_classname(self): + (rule,) = parse_stylesheet(".className {}") + self.assertEqual(selector_str(rule), ".className") + + def test_selector_str_id(self): + (rule,) = parse_stylesheet("#identifier {}") + self.assertEqual(selector_str(rule), "#identifier") + + def test_selector_str_with_brackets(self): + (rule,) = parse_stylesheet('input[type="text"] {}') + self.assertEqual(selector_str(rule), 'input[type="text"]') + + def test_selector_str_with_brackets_noquotes(self): + (rule,) = parse_stylesheet("input[type=text] {}") + self.assertEqual(selector_str(rule), "input[type=text]") + + def test_selector_str_with_comma(self): + (rule,) = parse_stylesheet("a, button {}") + self.assertEqual(selector_str(rule), "a, button") + + def test_selector_str_with_comma_and_newline(self): + (rule,) = parse_stylesheet("a,\nbutton {}") + self.assertEqual(selector_str(rule), "a, button") + + def test_selector_str_pseudoclass(self): + (rule,) = parse_stylesheet("a:visited {}") + self.assertEqual(selector_str(rule), "a:visited") + + def test_selector_str_pseudoclass_nonstandard(self): + (rule,) = parse_stylesheet("button::-moz-focus-inner {}") + self.assertEqual(selector_str(rule), "button::-moz-focus-inner") + + +SCSS_NOSHADOW_MIXIN_HEADER = """\ +// Trac uses box-shadow and text-shadow everywhere but their 90s look doesn't +// fit well with our design. +@mixin noshadow { + box-shadow: none; + border-radius: unset; +} +""" + + +if __name__ == "__main__": + parser = get_parser() + options = parser.parse_args() + + if options.tests: + NoShadowTestCase.run_and_exit() + + echo = partial(print, file=options.outfile) + + echo( + f"// Generated by {Path(__file__).name} on {datetime.now().isoformat()}", + end="\n\n", + ) + echo(SCSS_NOSHADOW_MIXIN_HEADER) + echo() + + for i, filepath in enumerate(sorted(options.cssfiles)): + rules = parse_stylesheet( + filepath.read_text(), skip_comments=True, skip_whitespace=True + ) + shadowrules = list(find_shadow(rules)) + if shadowrules: + if i > 0: + echo() + echo() + echo(f"// {filepath.name}") + combined_selector = ",\n".join(map(selector_str, shadowrules)) + echo(reset_css_str(combined_selector)) diff --git a/scss/_noshadows.scss b/scss/_noshadows.scss index d2a2be0..b95f4de 100644 --- a/scss/_noshadows.scss +++ b/scss/_noshadows.scss @@ -1,135 +1,96 @@ +// Generated by noshadows.py on 2024-01-18T15:52:50.928155 + // Trac uses box-shadow and text-shadow everywhere but their 90s look doesn't // fit well with our design. -// I grepped through Trac's source and undid all the box/text-shadows: -// $ git grep -n box-shadow -- trac/htdocs/css/ | grep -vF 'shadow: none' - -// trac.css -h1, h2, h3, h4, h5, h6, span { - &:target { - box-shadow: none; - } -} -input[type=button], input[type=submit], input[type=reset], .trac-button { - box-shadow: none; - text-shadow: none; - &:hover { - box-shadow: none; - text-shadow: none; - } -} -fieldset { - box-shadow: none; -} -.inlinebuttons { - input[type=button], - input[type=submit], - .trac-button, - a { - &:hover { - box-shadow: none; - text-shadow: none; - } - } -} -#mainnav { - box-shadow: none; - :link, :visited { - box-shadow: none; - &:hover { - box-shadow: none; - } - } - .active { - :link, :visited { - text-shadow: none; - } - } -} -div.trac-content { - box-shadow: none; -} -.foldable { - :link, :visited { +@mixin noshadow { box-shadow: none; - text-shadow: none; - } + border-radius: unset; } -fieldset > legend.foldable { - :link, :visited { - text-shadow: none; - } -} -#prefs { - box-shadow: none; + + +// admin.css +#tabs, +#tabs li li.active, +.plugin{ + @include noshadow; } -pre.wiki, pre.literal-block { - box-shadow: none; + + +// browser.css +div.message{ + @include noshadow; } -table.wiki { - box-shadow: none; + + +// code.css +div.code, +table.code{ + @include noshadow; } -table.listing { - box-shadow: none; + + +// diff.css +.diff li.entry{ + @include noshadow; } -div.system-message { - box-shadow: none; - .trac-close-msg:hover { - box-shadow: none; - text-shadow: none; - } + + +// prefs.css +#content.prefs div.prefs_child h2{ + @include noshadow; } + // report.css -.report div.reports h2 { - box-shadow: none; -} -h2.report-result { - box-shadow: none; +.report div.reports h2, +h2.report-result{ + @include noshadow; } -// wiki.css -.wikipage h2 { - box-shadow: none; + +// roadmap.css +table.progress, +.milestone .info h2{ + @include noshadow; } + // ticket.css -#changelog, #ticketchange { - h3, .changes { - box-shadow: none; - } +ul.children > li.child, +#changelog .changes, #ticketchange .changes{ + @include noshadow; } -// admin.css -#tabs { - box-shadow: none; - li { - li.active { - box-shadow: none; - } - } -} -.plugin { - box-shadow: none; +// timeline.css +.timeline h2{ + @include noshadow; } -// browser.css -div.message { - box-shadow: none; -} -// prefs.css -#content.prefs div.prefs_child h2 { - box-shadow: none; +// trac.css +:link:hover, :visited:hover, +h1:target, h2:target, h3:target, h4:target, h5:target, h6:target, span:target, +input[type=button], input[type=submit], input[type=reset], .trac-button, +input[type=button]:hover, input[type=submit]:hover, input[type=reset]:hover, .trac-button:hover, +input[type=text], input.textwidget, textarea, +fieldset, +.inlinebuttons input[type=button]:hover, .inlinebuttons input[type=submit]:hover, .inlinebuttons .trac-button:hover, .inlinebuttons a:hover, +#metanav form.trac-logout button:hover, +#mainnav, +div.trac-content, +.foldable :link, .foldable :visited, +#prefs, +pre.wiki, pre.literal-block, +table.wiki, +table.listing, +div.system-message, +div.system-message .trac-close-msg:hover{ + @include noshadow; } -// roadmap.css -table.progress, -.milestone .info h2 { - box-shadow: none; -} -// timeline.css -.timeline h2 { - box-shadow: none; +// wiki.css +.wiki-toc{ + @include noshadow; } From e141b4cfadf7c6ae4c9008b03be8d0287ed1a1b3 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Fri, 4 Nov 2022 22:23:10 +0100 Subject: [PATCH 4/4] Updated trackhacks.scss to fix styling after update to 1.4 --- scss/trachacks.scss | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/scss/trachacks.scss b/scss/trachacks.scss index c826f68..52518a8 100644 --- a/scss/trachacks.scss +++ b/scss/trachacks.scss @@ -22,11 +22,22 @@ a:link, a:visited { } h1, h2, h3, h4, h5, h6, span { + &.section { + color: inherit; + background: none; + } + &.section + * { + margin-top: unset !important; + } &:target { background: $green-very-light; } } +[role="main"] .section { + padding-bottom: unset; +} + .inlinebuttons { input { border-radius: 5px; @@ -48,6 +59,14 @@ div.trac-content { } } +#content.narrow #wikipage { + max-width: none; +} + +#trac-wiki-expander { + display: none; +} + .full-width [role="main"] { max-width: 1400px; } @@ -157,8 +176,10 @@ blockquote.citation { } #ticket { - background-color: $white; - border-color: $gray-line; + #ticketbox { + background-color: $white; + border-color: $gray-line; + } table.properties { border-top-color: $gray-line; th, th.missing { @@ -247,11 +268,13 @@ div[role="main"]{ border-radius: 0; text-align: center; padding: 0.5em 0 0.5em 0; + font-weight: normal; a, a:hover { background: none; @include sans-serif; color: $green-dark; + text-transform: none; } &.first { padding-left: 0;