|
| 1 | +import itertools |
| 2 | +import pathlib |
| 3 | +from textwrap import dedent |
| 4 | + |
| 5 | +from truncatehtml import truncate |
| 6 | + |
| 7 | + |
| 8 | +def _generate_sorted_tag_keys(all_items): |
| 9 | + |
| 10 | + key_set = set(itertools.chain(*[item['tags'].keys() for item in all_items])) |
| 11 | + return sorted(key_set) |
| 12 | + |
| 13 | + |
| 14 | +def _generate_tag_set(all_items, tag_key=None): |
| 15 | + |
| 16 | + tag_set = set() |
| 17 | + for item in all_items: |
| 18 | + for k, e in item['tags'].items(): |
| 19 | + if tag_key and k != tag_key: |
| 20 | + continue |
| 21 | + for t in e: |
| 22 | + tag_set.add(t) |
| 23 | + |
| 24 | + return tag_set |
| 25 | + |
| 26 | + |
| 27 | +def _generate_tag_menu(all_items, tag_key): |
| 28 | + |
| 29 | + tag_set = _generate_tag_set(all_items, tag_key) |
| 30 | + tag_list = sorted(tag_set) |
| 31 | + |
| 32 | + options = ''.join( |
| 33 | + f'<li><label class="dropdown-item checkbox {tag_key}"><input type="checkbox" rel={tag.replace(" ", "-")} onchange="change();"> {tag.capitalize()}</label></li>' |
| 34 | + for tag in tag_list |
| 35 | + ) |
| 36 | + |
| 37 | + return f""" |
| 38 | +<div class="dropdown"> |
| 39 | +
|
| 40 | +<button class="btn btn-sm btn-outline-primary mx-1 dropdown-toggle" type="button" id="{tag_key}Dropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> |
| 41 | +{tag_key.title()} |
| 42 | +</button> |
| 43 | +<ul class="dropdown-menu" aria-labelledby="{tag_key}Dropdown"> |
| 44 | +{options} |
| 45 | +</ul> |
| 46 | +</div> |
| 47 | +""" |
| 48 | + |
| 49 | + |
| 50 | +def generate_menu(all_items, submit_btn_txt=None, submit_btn_link=None): |
| 51 | + |
| 52 | + key_list = _generate_sorted_tag_keys(all_items) |
| 53 | + |
| 54 | + menu_html = '<div class="d-sm-flex mt-3 mb-4">\n' |
| 55 | + menu_html += '<div class="d-flex gallery-menu">\n' |
| 56 | + if submit_btn_txt: |
| 57 | + menu_html += f'<div><a role="button" class="btn btn-primary btn-sm mx-1" href={submit_btn_link}>{submit_btn_txt}</a></div>\n' |
| 58 | + menu_html += '</div>\n' |
| 59 | + menu_html += '<div class="ml-auto d-flex">\n' |
| 60 | + menu_html += '<div><button class="btn btn-link btn-sm mx-1" onclick="clearCbs()">Clear all filters</button></div>\n' |
| 61 | + for tag_key in key_list: |
| 62 | + menu_html += _generate_tag_menu(all_items, tag_key) + '\n' |
| 63 | + menu_html += '</div>\n' |
| 64 | + menu_html += '</div>\n' |
| 65 | + menu_html += '<script>$(document).on("click",function(){$(".collapse").collapse("hide");}); </script>\n' |
| 66 | + return menu_html |
| 67 | + |
| 68 | + |
| 69 | +def build_from_items(items, filename, title='Gallery', subtitle=None, subtext=None, menu_html='', max_descr_len=300): |
| 70 | + |
| 71 | + # Build the gallery file |
| 72 | + panels_body = [] |
| 73 | + for item in items: |
| 74 | + if not item.get('thumbnail'): |
| 75 | + item['thumbnail'] = '/_static/images/ebp-logo.png' |
| 76 | + thumbnail = item['thumbnail'] |
| 77 | + tag_list = sorted((itertools.chain(*item['tags'].values()))) |
| 78 | + tag_list_f = [tag.replace(' ', '-') for tag in tag_list] |
| 79 | + |
| 80 | + tags = [f'<span class="badge bg-primary">{tag}</span>' for tag in tag_list_f] |
| 81 | + tags = '\n'.join(tags) |
| 82 | + |
| 83 | + tag_class_str = ' '.join(tag_list_f) |
| 84 | + |
| 85 | + author_strs = set() |
| 86 | + institution_strs = set() |
| 87 | + for a in item['authors']: |
| 88 | + author_name = a.get('name', 'Anonymous') |
| 89 | + author_email = a.get('email', None) |
| 90 | + if author_email: |
| 91 | + _str = f'<a href="mailto:{author_email}">{author_name}</a>' |
| 92 | + else: |
| 93 | + _str = author_name |
| 94 | + author_strs.add(_str) |
| 95 | + |
| 96 | + institution_name = a.get('institution', None) |
| 97 | + if institution_name: |
| 98 | + institution_url = a.get('institution_url', None) |
| 99 | + if institution_url: |
| 100 | + _str = f'<a href="{institution_url}">{institution_name}</a>' |
| 101 | + else: |
| 102 | + _str = institution_name |
| 103 | + institution_strs.add(_str) |
| 104 | + |
| 105 | + authors_str = f"<strong>Author:</strong> {', '.join(author_strs)}" |
| 106 | + if institution_strs: |
| 107 | + institutions_str = f"<strong>Institution:</strong> {' '.join(institution_strs)}" |
| 108 | + else: |
| 109 | + institutions_str = '' |
| 110 | + |
| 111 | + ellipsis_str = '<a class="modal-btn"> ... more</a>' |
| 112 | + short_description = truncate(item['description'], max_descr_len, ellipsis=ellipsis_str) |
| 113 | + |
| 114 | + if ellipsis_str in short_description: |
| 115 | + modal_str = f""" |
| 116 | +<div class="modal"> |
| 117 | +<div class="content"> |
| 118 | +<img src="{thumbnail}" class="modal-img" /> |
| 119 | +<h3 class="display-3">{item["title"]}</h3> |
| 120 | +{authors_str} |
| 121 | +<br/> |
| 122 | +{institutions_str} |
| 123 | +<p class="my-2">{item['description']}</p> |
| 124 | +<p class="my-2">{tags}</p> |
| 125 | +<p class="mt-3 mb-0"><a href="{item["url"]}" class="btn btn-outline-primary btn-block">Visit Website</a></p> |
| 126 | +</div> |
| 127 | +</div> |
| 128 | +""" |
| 129 | + else: |
| 130 | + modal_str = '' |
| 131 | + |
| 132 | + panels_body.append( |
| 133 | + f"""\ |
| 134 | +--- |
| 135 | +:column: + tagged-card {tag_class_str} |
| 136 | +
|
| 137 | +<div class="d-flex gallery-card"> |
| 138 | +<img src="{thumbnail}" class="gallery-thumbnail" /> |
| 139 | +<div class="container"> |
| 140 | +<a href="{item["url"]}" class="text-decoration-none"><h4 class="display-4 p-0">{item["title"]}</h4></a> |
| 141 | +<p class="card-subtitle">{authors_str}<br/>{institutions_str}</p> |
| 142 | +<p class="my-2">{short_description}</p> |
| 143 | +</div> |
| 144 | +</div> |
| 145 | +{modal_str} |
| 146 | +
|
| 147 | ++++ |
| 148 | +
|
| 149 | +{tags} |
| 150 | +
|
| 151 | +""" |
| 152 | + ) |
| 153 | + |
| 154 | + panels_body = '\n'.join(panels_body) |
| 155 | + |
| 156 | + stitle = f'#### {subtitle}' if subtitle else '' |
| 157 | + stext = subtext if subtext else '' |
| 158 | + |
| 159 | + panels = f""" |
| 160 | +# {title} |
| 161 | +
|
| 162 | +{stitle} |
| 163 | +{stext} |
| 164 | +
|
| 165 | +{menu_html} |
| 166 | +
|
| 167 | +````{{panels}} |
| 168 | +:column: col-12 |
| 169 | +:card: +mb-4 w-100 |
| 170 | +:header: d-none |
| 171 | +:body: p-3 m-0 |
| 172 | +:footer: p-1 |
| 173 | +
|
| 174 | +{dedent(panels_body)} |
| 175 | +```` |
| 176 | +
|
| 177 | +<div class="modal-backdrop"></div> |
| 178 | +<script src="/_static/custom.js"></script> |
| 179 | +""" |
| 180 | + |
| 181 | + pathlib.Path(f'{filename}.md').write_text(panels) |
0 commit comments