├── .dockerignore ├── .github └── workflows │ ├── build.yml │ ├── lints.yml │ └── tests.yml ├── .gitignore ├── LICENSE ├── README.md ├── docker-compose.override.yml ├── docker-compose.yml ├── docker ├── Dockerfile ├── clean.sh ├── dump.sh ├── init.sh ├── minio │ └── entrypoint.sh └── pandoc-lambda │ ├── Dockerfile │ ├── README.md │ ├── entrypoint.sh │ ├── function │ ├── .gitignore │ ├── README.md │ ├── app.py │ ├── old_pr1491 │ │ ├── reference.docx │ │ └── table_of_contents.lua │ ├── reference.docx │ ├── requirements.in │ ├── requirements.txt │ ├── setup.cfg │ └── table_of_contents.lua │ └── reference_docx │ ├── README.md │ ├── reference.docx │ ├── src │ ├── [Content_Types].xml │ ├── _rels │ │ └── .rels │ ├── docProps │ │ ├── app.xml │ │ ├── core.xml │ │ └── custom.xml │ └── word │ │ ├── _rels │ │ ├── document.xml.rels │ │ └── footnotes.xml.rels │ │ ├── comments.xml │ │ ├── document.xml │ │ ├── fontTable.xml │ │ ├── footer_blank.xml │ │ ├── footer_title.xml │ │ ├── footnotes.xml │ │ ├── header_blank.xml │ │ ├── header_even.xml │ │ ├── header_front_matter_even.xml │ │ ├── header_front_matter_odd.xml │ │ ├── header_odd.xml │ │ ├── numbering.xml │ │ ├── settings.xml │ │ ├── styles.xml │ │ ├── theme │ │ └── theme1.xml │ │ └── webSettings.xml │ ├── style_structure_reference.md │ ├── update_docx_from_xml.zsh │ └── update_style_structure_md.py ├── package-lock.json ├── pyproject.toml └── web ├── .gitignore ├── babel.config.js ├── codecov.yml ├── config ├── __init__.py ├── context_processors.py ├── settings │ ├── __init__.py │ ├── settings.py.example │ ├── settings_base.py │ ├── settings_dev.py │ ├── settings_prod.py │ └── settings_pytest.py ├── urls.py └── wsgi.py ├── conftest.py ├── frontend ├── components │ ├── AddContent.vue │ ├── AnnotationBase.vue │ ├── AnnotationExpansionToggle.vue │ ├── AnnotationHandle.vue │ ├── AuditButton.vue │ ├── CaseResults.vue │ ├── CaseSearcher.vue │ ├── CollapseTriangle.vue │ ├── ContextMenu.vue │ ├── CorrectionAnnotation.vue │ ├── Dashboard.vue │ ├── Dashboard │ │ └── Casebook.vue │ ├── ElisionAnnotation.vue │ ├── FootnoteLink.vue │ ├── Globals.vue │ ├── HighlightAnnotation.vue │ ├── LegalDocumentSearch │ │ ├── AdvancedSearch.vue │ │ ├── LegalDocumentSearch.vue │ │ ├── ResultsForm.vue │ │ └── SearchForm.vue │ ├── LinkAnnotation.vue │ ├── LinkInput.vue │ ├── LoadingSpinner.vue │ ├── Modal.vue │ ├── NoteAnnotation.vue │ ├── PublishButton.vue │ ├── QuickAdd.vue │ ├── ReplacementAnnotation.vue │ ├── SectionCloner.vue │ ├── SideMenu.vue │ ├── SpacePreserver.vue │ ├── TableOfContents │ │ ├── Entry.vue │ │ ├── EntryAuditor.vue │ │ ├── EntryTransmuter.vue │ │ └── PlaceHolder.vue │ ├── TakeNotesCloner.vue │ ├── TheAnnotator.vue │ ├── TheGlobalElisionExpansionButton.vue │ ├── TheResource.vue │ ├── TheResourceBody.vue │ ├── TheTableOfContents.vue │ └── TinyMCEEditor.vue ├── config │ └── axios.js ├── directives │ └── selectionchange.js ├── fonts │ ├── AtlasGrotesk-Bold.eot │ ├── AtlasGrotesk-Bold.svg │ ├── AtlasGrotesk-Bold.ttf │ ├── AtlasGrotesk-Bold.woff │ ├── AtlasGrotesk-Light.eot │ ├── AtlasGrotesk-Light.svg │ ├── AtlasGrotesk-Light.ttf │ ├── AtlasGrotesk-Light.woff │ ├── AtlasGrotesk-Medium.eot │ ├── AtlasGrotesk-Medium.svg │ ├── AtlasGrotesk-Medium.ttf │ ├── AtlasGrotesk-Medium.woff │ ├── AtlasGrotesk-Regular.eot │ ├── AtlasGrotesk-Regular.svg │ ├── AtlasGrotesk-Regular.ttf │ ├── AtlasGrotesk-Regular.woff │ ├── ChronicleTextG3-Roman.eot │ ├── ChronicleTextG3-Roman.svg │ ├── ChronicleTextG3-Roman.ttf │ ├── ChronicleTextG3-Roman.woff │ ├── RobotoMono-Bold.ttf │ ├── tinymce-small.eot │ ├── tinymce-small.svg │ ├── tinymce-small.ttf │ ├── tinymce-small.woff │ ├── tinymce.eot │ ├── tinymce.svg │ ├── tinymce.ttf │ └── tinymce.woff ├── legacy │ ├── README.txt │ ├── lib │ │ ├── bootstrap │ │ │ ├── dropdown.js │ │ │ └── modal.js │ │ ├── helpers.js │ │ ├── requests.js │ │ └── ui │ │ │ ├── component.js │ │ │ ├── content │ │ │ ├── annotate_modal.js │ │ │ ├── clone_modal.js │ │ │ ├── dashboard.js │ │ │ ├── export_modal.js │ │ │ ├── index.js │ │ │ └── revise_modal.js │ │ │ ├── filter-selectize.js │ │ │ ├── modal.js │ │ │ └── skip-link-focus-fix.js │ └── polyfills │ │ ├── index.js │ │ └── promises.js ├── libs │ ├── html_helpers.js │ ├── legal_document_search.js │ ├── resource_node_parsing.js │ ├── text_outline_parser.js │ ├── tinymce_extensions.js │ └── urls.js ├── pages │ ├── application.js │ ├── main.scss │ ├── rich_text_editor.js │ ├── table_of_contents.js │ ├── test.js │ └── vue_app.js ├── store │ ├── index.js │ └── modules │ │ ├── annotations.js │ │ ├── annotations_ui.js │ │ ├── case_search.js │ │ ├── footnotes_ui.js │ │ ├── globals.js │ │ ├── resources_ui.js │ │ └── table_of_contents.js ├── styles │ ├── announcement_banner.scss │ ├── buttons.scss │ ├── casebook.scss │ ├── cases.scss │ ├── ckeditor_styles.scss │ ├── content │ │ ├── annotations.scss │ │ ├── dragging.scss │ │ └── modals.scss │ ├── credits.scss │ ├── dashboard.scss │ ├── font-faces.scss │ ├── font-vars-and-mixins.scss │ ├── footer.scss │ ├── forms.scss │ ├── header.scss │ ├── jquery.ui.custom.scss │ ├── landing.scss │ ├── layout.scss │ ├── mixins.scss │ ├── mockups.scss │ ├── pages.scss │ ├── searches.scss │ ├── spinner.scss │ ├── text.scss │ ├── tinymce_editor.scss │ ├── ui.scss │ ├── uscode.scss │ ├── users.scss │ ├── variables.scss │ └── vars-and-mixins.scss └── test │ ├── .eslintrc.js │ ├── components │ ├── QuickAdd.test.js │ ├── ResultsForm.test.js │ ├── SearchForm.test.js │ ├── TheAnnotator.test.js │ └── TheResourceBody.test.js │ ├── libs │ ├── example_tocs │ │ ├── baker-national-security-law-2015.txt │ │ ├── beebe-trademark-law.txt │ │ ├── colin-miller-best-evidence.txt │ │ ├── feldman-constitutional-law-separation-of-powers-2015.txt │ │ ├── geier-2020.txt │ │ ├── hatfield-ethics-tax-lawyering-3rd.txt │ │ ├── levin-civil-procedure-pleading.txt │ │ ├── mcfarland-park-computer-aided-civil-procedure.txt │ │ └── verkerke-contract-doctrine.txt │ ├── html_helpers.test.js │ └── resource_node_parsing.test.js │ ├── mocha_setup.js │ └── test_helpers.js ├── main ├── __init__.py ├── admin.py ├── apps.py ├── authenticator.py ├── case_xml_converter.py ├── create_fts_index.sql ├── create_search_index.sql ├── differ.py ├── export.py ├── forms.py ├── hashers.py ├── legal_document_sources.py ├── middleware.py ├── migrations │ ├── 0001_squashed_0072_auto_20201015_1225.py │ ├── 0002_auto_20201021_1444.py │ ├── 0003_alternate_legal_docs.py │ ├── 0004_auto_20210405_1927.py │ ├── 0005_auto_20210414_1240.py │ ├── 0006_uscodeindex_repealed.py │ ├── 0007_savedimage.py │ ├── 0008_auto_20210511_1533.py │ ├── 0009_auto_20210512_0217.py │ ├── 0010_auto_20210512_0257.py │ ├── 0011_auto_20210805_1723.py │ ├── 0012_auto_20210914_0057.py │ ├── 0013_casebookfollow.py │ ├── 0014_auto_20211007_1248.py │ ├── 0015_auto_20211014_0041.py │ ├── 0016_auto_20211025_2147.py │ ├── 0017_auto_20211130_2114.py │ ├── 0018_auto_20211130_2236.py │ ├── 0019_livesettings.py │ ├── 0020_auto_20220209_1732.py │ ├── 0021_auto_20220527_1714.py │ ├── 0022_post_32_upgrade.py │ ├── 0023_drop_legacy_annotation_fields.py │ ├── 0024_drop_raw_headnote.py │ ├── 0025_drop_created_via_import.py │ ├── 0026_drop_public_on_textblock.py │ ├── 0027_drop_legacy_tables.py │ ├── 0028_fulltextsearchindex.py │ ├── 0029_auto_20220714_1747.py │ ├── 0030_reading_length.py │ ├── 0031_casebook_description_20220719_1550.py │ ├── 0032_setting_for_html_export.py │ ├── 0033_improve_commontitle_docs.py │ ├── 0034_add_instructional_content_field.py │ ├── 0035_user_details.py │ ├── 0036_instructional_material_help_text.py │ ├── 0037_add_institution_model.py │ ├── 0038_initial_institution_data.py │ ├── 0039_drop_printable_livesetting.py │ ├── 0040_drop_link_content_type_field.py │ ├── 0041_add_casebook_tags.py │ ├── 0042_legaldocument_indexes.py │ ├── 0043_add_listed_publicly_field.py │ ├── 0044_add_user_groups.py │ ├── 0045_add_courtlistener_api_source.py │ ├── 0046_auto_20240516_1322.py │ ├── 0047_auto_20240516_1754.py │ └── __init__.py ├── models.py ├── reporter.py ├── sanitize.py ├── serializers.py ├── storages.py ├── templates │ ├── 400.html │ ├── 403.html │ ├── 403_csrf.html │ ├── 404.html │ ├── 500.html │ ├── admin │ │ ├── change_form_with_richeditor.html │ │ ├── h2o_index.html │ │ ├── input_filter.html │ │ ├── main │ │ │ ├── casebook │ │ │ │ ├── change_form.html │ │ │ │ └── inline_series.html │ │ │ ├── legaldocument │ │ │ │ └── change_form.html │ │ │ ├── link │ │ │ │ └── change_form.html │ │ │ ├── resource │ │ │ │ └── change_form.html │ │ │ ├── section │ │ │ │ └── change_form.html │ │ │ ├── textblock │ │ │ │ └── change_form.html │ │ │ └── user │ │ │ │ └── change_form.html │ │ └── reporting │ │ │ └── index.html │ ├── archived_casebooks.html │ ├── base.html │ ├── casebook_history.html │ ├── casebook_outline_edit.html │ ├── casebook_page.html │ ├── casebook_page_credits.html │ ├── casebook_page_related.html │ ├── casebook_page_search.html │ ├── casebook_settings.html │ ├── dashboard.html │ ├── email │ │ └── welcome.txt │ ├── export │ │ ├── about.html │ │ ├── as_printable_html │ │ │ ├── casebook.html │ │ │ ├── credits.html │ │ │ ├── node.html │ │ │ ├── tbd.html │ │ │ └── toc.html │ │ ├── casebook.html │ │ ├── credits.html │ │ ├── node.html │ │ ├── section.html │ │ └── tbd.html │ ├── export_error.html │ ├── includes │ │ ├── action_buttons.html │ │ ├── analytics.html │ │ ├── announcement_banner.html │ │ ├── bodies │ │ │ ├── case.html │ │ │ ├── empty.html │ │ │ ├── legal_doc.html │ │ │ ├── link.html │ │ │ └── text_block.html │ │ ├── breadcrumbs.html │ │ ├── case_header.html │ │ ├── casebook_copyright_notice.html │ │ ├── casebook_page_tabs.html │ │ ├── casebook_search.html │ │ ├── collaborators.html │ │ ├── content_browser.html │ │ ├── credits.html │ │ ├── featured_casebook.html │ │ ├── footer.html │ │ ├── header.html │ │ ├── headnote.html │ │ ├── legal_doc_sources │ │ │ ├── cap_header.html │ │ │ ├── court_listener_header.html │ │ │ ├── empty_header.html │ │ │ └── gpo_header.html │ │ ├── page_buttons.html │ │ ├── preview_banner.html │ │ ├── reading_mode_toc_item.html │ │ ├── resource_tabs.html │ │ ├── section_tabs.html │ │ ├── sentry.html │ │ └── table-of-contents.html │ ├── index.html │ ├── legal_doc.html │ ├── pages │ │ ├── about.html │ │ ├── featured_casebooks.html │ │ ├── privacy-policy.html │ │ └── terms-of-service.html │ ├── registration │ │ ├── login.html │ │ ├── password_change_done.html │ │ ├── password_change_form.html │ │ ├── password_reset_complete.html │ │ ├── password_reset_confirm.html │ │ ├── password_reset_done.html │ │ ├── password_reset_form.html │ │ └── sign_up.html │ ├── resource_annotate.html │ ├── robots.txt │ ├── search │ │ ├── filters.html │ │ ├── pagination.html │ │ ├── results.html │ │ └── show.html │ └── user_edit.html ├── templatetags │ ├── call_method.py │ ├── current_query_string.py │ ├── export_node_html.py │ ├── featured_casebook.py │ ├── humanize_minutes.py │ ├── reading_mode_toc_item.py │ ├── short_page_range.py │ └── string_strip.py ├── test │ ├── __init__.py │ ├── functional │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── fixtures │ │ │ ├── casebooks.json │ │ │ ├── contentannotation.json │ │ │ ├── contentcollaborators.json │ │ │ ├── contentnodes.json │ │ │ ├── textblocks.json │ │ │ └── users.json │ │ ├── test_platform.py │ │ └── test_reading_mode.py │ ├── test_admin.py │ ├── test_auth.py │ ├── test_cloning.py │ ├── test_credits.py │ ├── test_drafts.py │ ├── test_editing.py │ ├── test_errors.py │ ├── test_export.py │ ├── test_forms.py │ ├── test_permissions.py │ ├── test_permissions_helpers.py │ ├── test_publishing.py │ ├── test_search.py │ ├── test_templatetags.py │ ├── test_user_profile.py │ └── views.py ├── url_converters.py ├── urls.py ├── utils.py └── views.py ├── manage.py ├── package-lock.json ├── package.json ├── reporting ├── __init__.py ├── admin │ ├── __init__.py │ ├── usage_dashboard.py │ └── views.py ├── apps.py ├── create_reporting_views.py ├── matomo.py ├── migrations │ ├── 0001_add_reporting_proxy_models.py │ ├── 0002_plural_name_reporting.py │ └── __init__.py ├── models.py ├── sql │ └── reporting.sql ├── tests │ ├── test_matomo_integration.py │ └── test_reporting_views.py ├── urls.py └── views.py ├── requirements.in ├── requirements.txt ├── setup.cfg ├── static ├── as_printable_html │ ├── all.css │ ├── as_printable_html.js │ ├── cap.css │ ├── pagedjs.js │ ├── print.css │ ├── print.js │ ├── screen.css │ ├── toc.css │ └── uscode.css ├── data │ └── ali_materials.json ├── dist │ ├── css │ │ ├── main.8e68f311.css │ │ └── vue_app.62b395e0.css │ ├── fonts │ │ ├── AtlasGrotesk-Light.bcfd7cf6.woff │ │ ├── AtlasGrotesk-Light.be1c731f.eot │ │ ├── AtlasGrotesk-Light.e69872f9.ttf │ │ ├── AtlasGrotesk-Medium.14791ebe.eot │ │ ├── AtlasGrotesk-Medium.83bc0a62.woff │ │ ├── AtlasGrotesk-Medium.d9cc5003.ttf │ │ ├── AtlasGrotesk-Regular.1f499573.ttf │ │ ├── AtlasGrotesk-Regular.94001d71.eot │ │ ├── AtlasGrotesk-Regular.d2997fb4.woff │ │ ├── ChronicleTextG3-Roman.6a453768.ttf │ │ ├── ChronicleTextG3-Roman.f77c0515.woff │ │ ├── ChronicleTextG3-Roman.f9c467a3.eot │ │ ├── RobotoMono-Bold.c0c4a337.ttf │ │ ├── glyphicons-halflings-regular.448c34a5.woff2 │ │ ├── glyphicons-halflings-regular.e18bbf61.ttf │ │ ├── glyphicons-halflings-regular.f4769f9b.eot │ │ └── glyphicons-halflings-regular.fa277232.woff │ ├── img │ │ ├── AtlasGrotesk-Light.219d1463.svg │ │ ├── AtlasGrotesk-Medium.3e829f26.svg │ │ ├── AtlasGrotesk-Regular.26133821.svg │ │ ├── ChronicleTextG3-Roman.0e3646cf.svg │ │ ├── Link.148d855f.svg │ │ ├── add-casebook.ff004159.svg │ │ ├── add-material.d109215f.svg │ │ ├── add-section.a7b07d92.svg │ │ ├── audit-casebook.f96e34fb.svg │ │ ├── banner-draft-icon.514d5145.svg │ │ ├── cancel-icon.101f571f.svg │ │ ├── clone.fc6eb4b4.svg │ │ ├── customize-casebook.d78e11a3.svg │ │ ├── edit-icon.d38c8fff.svg │ │ ├── expand-arrow.6bf1cea3.svg │ │ ├── export-anim.c043e5fa.svg │ │ ├── export-html.a6acb1d4.svg │ │ ├── export.13e242d7.svg │ │ ├── external-link-icon.3e1fd22b.svg │ │ ├── follow.2e7d7ca7.svg │ │ ├── glyphicons-halflings-regular.89889688.svg │ │ ├── landing-demo.769625d8.png │ │ ├── link-go.4122dfcb.svg │ │ ├── lock.0ee492f3.svg │ │ ├── logo-icon-white-on-blue.f2cf816b.svg │ │ ├── logo.454a3835.svg │ │ ├── next_page.13726d8a.svg │ │ ├── prev_page.8adf0763.svg │ │ ├── preview.a015216b.svg │ │ ├── publish.9455f1e6.svg │ │ ├── save-icon.82eb3293.svg │ │ ├── search-icon.8c383980.svg │ │ ├── take-notes-icon.26352bcb.svg │ │ └── unfollow.54f63882.svg │ └── js │ │ ├── application.40739f7b.js │ │ ├── application.40739f7b.js.map │ │ ├── chunk-common.5c389b4d.js │ │ ├── chunk-common.5c389b4d.js.map │ │ ├── main.8272c3ff.js │ │ ├── main.8272c3ff.js.map │ │ ├── rich_text_editor.6f43d27a.js │ │ ├── rich_text_editor.6f43d27a.js.map │ │ ├── test.2ff30b1f.js │ │ ├── test.2ff30b1f.js.map │ │ ├── vue_app.d14b6f2a.js │ │ └── vue_app.d14b6f2a.js.map ├── fonts │ ├── AtlasGrotesk-Bold.woff2 │ ├── AtlasGrotesk-Regular.woff2 │ ├── ChronicleTextG3-Bold.woff2 │ ├── ChronicleTextG3-Italic.woff2 │ ├── ChronicleTextG3-Regular.woff2 │ ├── LibreCaslonText-Bold.woff2 │ ├── LibreCaslonText-Italic.woff2 │ ├── LibreCaslonText-Regular.woff2 │ └── OFL.txt ├── images │ ├── .keep │ ├── Link.svg │ ├── add-icon.png │ ├── arrow-search.png │ ├── banner-draft-icon.svg │ ├── close.png │ ├── endorsers │ │ ├── cohen.png │ │ ├── fried.png │ │ ├── fried2.png │ │ ├── modirzadeh.png │ │ ├── quinn.png │ │ ├── suk-gersen.png │ │ └── zittrain.png │ ├── expand-arrow.svg │ ├── external-link-icon.svg │ ├── favicon.ico │ ├── h20-logo.png │ ├── icons.png │ ├── landing-demo.png │ ├── logo-blue-wordmark.svg │ ├── logo-color-full-lockup.svg │ ├── logo-color-wordmark.svg │ ├── logo-icon-blue-on-yellow.svg │ ├── logo-icon-white-on-blue.svg │ ├── logo-white.png │ ├── logo.svg │ ├── mockups │ │ ├── landing.png │ │ └── new-casebook.png │ ├── quickbar.png │ ├── repeat_bg.png │ ├── school-logos │ │ ├── berkeley.png │ │ ├── harvard.png │ │ ├── michigan.png │ │ ├── penn.png │ │ ├── stanford.png │ │ └── yale.png │ ├── search-icon.svg │ ├── search.png │ ├── take-notes-icon.svg │ ├── tinymce_icons.png │ ├── transparent.gif │ └── ui │ │ ├── casebook │ │ ├── add-casebook.svg │ │ ├── add-material.svg │ │ ├── add-section.svg │ │ ├── audit-casebook.svg │ │ ├── cancel-icon.svg │ │ ├── clone.svg │ │ ├── customize-casebook.svg │ │ ├── edit-icon.svg │ │ ├── export-anim.svg │ │ ├── export-html.svg │ │ ├── export.svg │ │ ├── follow.svg │ │ ├── link-go.svg │ │ ├── lock.svg │ │ ├── next_page.svg │ │ ├── prev_page.svg │ │ ├── preview.svg │ │ ├── publish.svg │ │ ├── save-icon.svg │ │ └── unfollow.svg │ │ ├── edit │ │ ├── add.png │ │ └── elide.png │ │ ├── verified-large.png │ │ └── verified.png └── tinymce_skin │ ├── README.txt │ ├── content.css │ ├── content.inline.css │ ├── content.inline.min.css │ ├── content.min.css │ ├── content.mobile.css │ ├── content.mobile.min.css │ ├── fonts │ └── tinymce-mobile.woff │ ├── skin.css │ ├── skin.min.css │ ├── skin.mobile.css │ └── skin.mobile.min.css ├── tasks.py ├── test ├── __init__.py ├── files │ └── export │ │ ├── export-casebook-no-annotations.docx │ │ ├── export-casebook-no-annotations.html │ │ ├── export-casebook-with-annotations.docx │ │ ├── export-casebook-with-annotations.html │ │ ├── export-resource-no-annotations.docx │ │ ├── export-resource-no-annotations.html │ │ ├── export-resource-with-annotations.docx │ │ ├── export-resource-with-annotations.html │ │ ├── export-section-no-annotations.docx │ │ ├── export-section-no-annotations.html │ │ ├── export-section-with-annotations.docx │ │ └── export-section-with-annotations.html └── test_helpers.py ├── vue.config.js └── webpack-stats.json /.dockerignore: -------------------------------------------------------------------------------- 1 | ** 2 | !Dockerfile 3 | !web/requirements.txt 4 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build images 2 | 3 | on: workflow_dispatch 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Push docker images 10 | uses: harvard-lil/docker-compose-update-action@main 11 | with: 12 | registry: "registry.lil.tools" 13 | registry-user: ${{ secrets.REPOSITORY_USER }} 14 | registry-pass: ${{ secrets.REPOSITORY_TOKEN }} 15 | bake-action: "push" 16 | -------------------------------------------------------------------------------- /.github/workflows/lints.yml: -------------------------------------------------------------------------------- 1 | name: Lints 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: actions/setup-python@v5 12 | with: 13 | python-version: '3.11' 14 | - name: flake8 15 | run: | 16 | pip install `egrep -o 'flake8==\S+' web/requirements.txt` # install our version of flake8 17 | flake8 web/ --config web/setup.cfg 18 | flake8 docker/pandoc-lambda/function/ --config web/setup.cfg 19 | 20 | - name: black 21 | run: | 22 | pip install `egrep -o 'black==\S+' web/requirements.txt` # install our version of black 23 | black --check --diff . # Uses pyproject.toml 24 | 25 | - name: mypy 26 | run: | 27 | cd web 28 | pip install -r requirements.txt # install a full environment for mypy 29 | PYTHONPATH=. mypy 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | .DS_Store 3 | .rvmrc 4 | *.swp 5 | bin/phantomjs 6 | config/initializers/h2o_secret.rb 7 | config/newrelic.yml 8 | config/skylight.yml 9 | coverage/ 10 | locals/ 11 | log/ 12 | node_modules/ 13 | public/assets/ 14 | public/base.html 15 | public/cases/ 16 | public/ckeditor_assets/ 17 | public/collages/ 18 | public/exports/ 19 | public/iframe/ 20 | public/index.html 21 | public/index.html.gz 22 | public/javascripts/all.js 23 | public/p/ 24 | public/playlists/ 25 | public/stylesheets/all.css 26 | public/svg_icons/ 27 | public/system/users/images/ 28 | solr/data/ 29 | solr/pids/ 30 | tmp/ 31 | guard-sunspot/ 32 | .idea 33 | /.env 34 | .tern-port 35 | /public/packs 36 | /public/packs-test 37 | yarn-debug.log* 38 | .yarn-integrity 39 | /yarn-error.log 40 | .dir-locals.el 41 | *.dump 42 | .byebug_history 43 | .tmp 44 | .venv 45 | python-shell.sh 46 | 47 | # IntelliJ IDEA 48 | *.iml 49 | .vscode 50 | .mypy_cache 51 | 52 | # Dev-only webpack stats 53 | web/webpack-stats-serve.json 54 | 55 | # Playwright output 56 | web/test-results -------------------------------------------------------------------------------- /docker-compose.override.yml: -------------------------------------------------------------------------------- 1 | # overrides for local development, not used in CI 2 | services: 3 | pandoc-lambda: 4 | volumes: 5 | - ./docker/pandoc-lambda/function/:/function 6 | build: 7 | context: ./docker/pandoc-lambda 8 | x-bake: 9 | tags: 10 | - registry.lil.tools/harvardlil/h2o-pandoc-lambda:0.66-f4ef497565cd325e6161cb6b0dafe814 11 | platforms: 12 | - linux/amd64 13 | - linux/arm64 14 | x-hash-paths: 15 | - . 16 | web: 17 | build: 18 | context: . 19 | dockerfile: ./docker/Dockerfile 20 | x-bake: 21 | tags: 22 | - registry.lil.tools/harvardlil/h2o-python:0.117-13caa7e3bb2533fd5a1859b3854d9612 23 | platforms: 24 | - linux/amd64 25 | - linux/arm64 26 | x-hash-paths: 27 | - web/requirements.txt 28 | environment: 29 | - CAPAPI_API_KEY 30 | - GPO_API_KEY 31 | - COURTLISTENER_API_KEY 32 | - MATOMO_API_KEY 33 | - MATOMO_SITE_URL 34 | -------------------------------------------------------------------------------- /docker/clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | docker compose down --remove-orphans --rmi all --volumes 4 | -------------------------------------------------------------------------------- /docker/init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | display_usage() { 5 | echo "Seed dev database with data from a pg_dump file." 6 | echo 7 | echo "Usage:" 8 | echo " bash docker/init.sh -f dump_file" 9 | } 10 | 11 | # Help 12 | if [[ ( $1 == "--help") || ($1 == "-h")]]; then 13 | display_usage 14 | exit 0 15 | fi 16 | 17 | 18 | getopts ":f:" opt || true; 19 | case $opt in 20 | f) 21 | if [ -f "$OPTARG" ] 22 | then 23 | FILE=$OPTARG 24 | else 25 | echo "Invalid path." 26 | exit 1 27 | fi 28 | ;; 29 | \?) 30 | # illegal option or argument 31 | display_usage 32 | exit 1 33 | ;; 34 | :) 35 | # -f present, but no path provided 36 | echo "Please specify the path." 37 | exit 1 38 | ;; 39 | esac 40 | if [[ $((OPTIND - $#)) -ne 1 ]]; then 41 | # wrong number of args 42 | display_usage 43 | exit 1 44 | fi 45 | 46 | echo "Loading data from $FILE ..." 47 | docker cp "$FILE" "$(docker compose ps -q db)":/tmp/data.dump 48 | docker compose exec db pg_restore --username=postgres --verbose --no-owner -h localhost -d postgres /tmp/data.dump 49 | docker compose exec db rm -f /tmp/data.dump 50 | -------------------------------------------------------------------------------- /docker/minio/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # see https://docs.docker.com/engine/reference/builder/#entrypoint 3 | set -e 4 | 5 | # Initialize a bucket for images 6 | mkdir -p "$DATA_DIR/$BUCKET" 7 | 8 | # Initialize a bucket for exports 9 | mkdir -p "$DATA_DIR/$EXPORT_BUCKET" 10 | 11 | # Initialize a bucket for PDF exports 12 | mkdir -p "$DATA_DIR/$PDF_EXPORT_BUCKET" 13 | 14 | # Pass the Docker CMD to the image's original entrypoint script. 15 | exec su -c "/usr/bin/docker-entrypoint.sh $*" 16 | -------------------------------------------------------------------------------- /docker/pandoc-lambda/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [[ ${1:0:4} = 'app.' ]]; then 5 | if [ -z "${AWS_LAMBDA_RUNTIME_API}" ]; then 6 | exec watchmedo auto-restart --directory=/function --recursive --patterns="*.py" -- /usr/local/bin/aws-lambda-rie python -m awslambdaric $@ 7 | else 8 | exec python -m awslambdaric $@ 9 | fi 10 | else 11 | $@ 12 | fi 13 | -------------------------------------------------------------------------------- /docker/pandoc-lambda/function/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | -------------------------------------------------------------------------------- /docker/pandoc-lambda/function/old_pr1491/reference.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/docker/pandoc-lambda/function/old_pr1491/reference.docx -------------------------------------------------------------------------------- /docker/pandoc-lambda/function/reference.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/docker/pandoc-lambda/function/reference.docx -------------------------------------------------------------------------------- /docker/pandoc-lambda/function/requirements.in: -------------------------------------------------------------------------------- 1 | pip-tools 2 | 3 | awslambdaric # AWS Lambda Runtime Interface Client 4 | boto3 # S3 file transfers 5 | lxml # Word editing 6 | python-docx 7 | watchdog # Restart dev server on file changes 8 | pyyaml # Required by watchdog 9 | -------------------------------------------------------------------------------- /docker/pandoc-lambda/function/setup.cfg: -------------------------------------------------------------------------------- 1 | ## http://flake8.pycqa.org/en/latest/user/configuration.html 2 | [flake8] 3 | ignore = E12, 4 | E2,W2, 5 | E3,W3, 6 | E4, 7 | E501, 8 | F403,F405 9 | # default ignore list via `flake8 --help` 10 | E121,E123,E126,E226,E24,E704,W503,W504 11 | -------------------------------------------------------------------------------- /docker/pandoc-lambda/reference_docx/reference.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/docker/pandoc-lambda/reference_docx/reference.docx -------------------------------------------------------------------------------- /docker/pandoc-lambda/reference_docx/src/_rels/.rels: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docker/pandoc-lambda/reference_docx/src/docProps/app.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 83 4 | false 5 | false 6 | 12 7 | 12.0000 8 | false 9 | Microsoft Word 12.0.0 10 | 583 11 | 12 | 0 13 | 6 14 | false 15 | 475 16 | 8 17 | 1 18 | -------------------------------------------------------------------------------- /docker/pandoc-lambda/reference_docx/src/docProps/core.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | Title 6 | Author 7 | 8 | 2017-12-27T05:22:50Z 9 | 2017-12-27T05:22:50Z 10 | -------------------------------------------------------------------------------- /docker/pandoc-lambda/reference_docx/src/docProps/custom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /docker/pandoc-lambda/reference_docx/src/word/_rels/footnotes.xml.rels: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docker/pandoc-lambda/reference_docx/src/word/comments.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docker/pandoc-lambda/reference_docx/src/word/footnotes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Footnote Text. -------------------------------------------------------------------------------- /docker/pandoc-lambda/reference_docx/src/word/webSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docker/pandoc-lambda/reference_docx/update_docx_from_xml.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | 4 | echo 5 | if read -q "choice?Update working reference.docx in THIS DIRECTORY? The subsequent two commands will use non-updated data if you don't: [y/N] (don't press return after)"; then 6 | rm ./reference.docx 7 | cd src || exit 8 | zip -r ../reference.docx * || cd - || exit 9 | cd - || exit 10 | else 11 | echo "Ok, Not Updating." 12 | fi 13 | 14 | echo 15 | if read -q "choice?Update Markdown Style Document? : [y/N] (don't press return after)"; then 16 | echo; echo "Updating..." 17 | python3 update_style_structure_md.py reference.docx style_structure_reference.md 18 | else 19 | echo "Ok, Not Updating." 20 | fi 21 | 22 | echo 23 | if read -q "choice?Overwrite the deployed reference.docx in the LAMBDA FUNCTION DIRECTORY with the version in THIS DIRECTORY? : "; then 24 | echo; echo "Overwriting version in code directory..." 25 | cp reference.docx ../function/ 26 | else 27 | echo "Ok, Not Replacing" 28 | fi 29 | echo 30 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 100 3 | extend-exclude = "web/.*/migrations/" 4 | include = "web/.*.py$" 5 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.sqlite3 3 | .python-version 4 | settings.py 5 | -------------------------------------------------------------------------------- /web/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@vue/app'] 4 | ] 5 | } -------------------------------------------------------------------------------- /web/codecov.yml: -------------------------------------------------------------------------------- 1 | # don't fail the PR on coverage decrease 2 | coverage: 3 | status: 4 | project: false 5 | patch: false 6 | changes: false 7 | 8 | # rewrite paths so "main/models.py" becomes "_python/main/models.py" in coverage file: 9 | fixes: 10 | - "::_python/" 11 | -------------------------------------------------------------------------------- /web/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/config/__init__.py -------------------------------------------------------------------------------- /web/config/settings/settings.py.example: -------------------------------------------------------------------------------- 1 | # This file is necessary only if you want to modify default settings from settings_dev.py. 2 | # To use, copy to settings.py: 3 | 4 | from .settings_dev import * # noqa 5 | 6 | 7 | # log all SQL statements: 8 | # LOGGING['loggers']['django.db.backends'] = { 9 | # 'level': 'DEBUG', 10 | # 'handlers': ['console'], 11 | # 'propagate': False, 12 | # } 13 | 14 | # If you want to load case text from CAP: 15 | CAPAPI_API_KEY = '' -------------------------------------------------------------------------------- /web/config/settings/settings_prod.py: -------------------------------------------------------------------------------- 1 | from .settings_base import * # noqa 2 | 3 | DEBUG = False 4 | 5 | SESSION_COOKIE_SECURE = True 6 | CSRF_COOKIE_SECURE = True 7 | 8 | # logging 9 | LOGGING["loggers"] = { 10 | "django": { 11 | "handlers": ["file", "mail_admins"], 12 | "level": "INFO", 13 | "propagate": True, 14 | }, 15 | "django.request": { 16 | "handlers": ["mail_admins"], 17 | "level": "ERROR", 18 | "propagate": False, 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /web/config/settings/settings_pytest.py: -------------------------------------------------------------------------------- 1 | # Django settings used by pytest 2 | 3 | # WARNING: this imports from .settings_dev instead of config.settings, meaning it chooses to IGNORE any settings in 4 | # config/settings/settings.py. This is potentially better (in that it doesn't return different results locally than 5 | # it will on CI), but also potentially worse (in that you can't try out settings tweaks in settings.py and run tests 6 | # against them). 7 | 8 | from .settings_dev import * 9 | 10 | TESTING = True 11 | 12 | # Don't use whitenoise for tests. Including whitenoise causes it to rescan static during each test, which greatly 13 | # increases test time. 14 | MIDDLEWARE.remove("whitenoise.middleware.WhiteNoiseMiddleware") 15 | 16 | CAPAPI_API_KEY = "12345" 17 | 18 | LOGGING["loggers"]["django"]["handlers"].append("mail_admins") 19 | 20 | COVER_IMAGES = True 21 | CELERY_TASK_ALWAYS_EAGER = False 22 | -------------------------------------------------------------------------------- /web/config/urls.py: -------------------------------------------------------------------------------- 1 | """config URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.2/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | 17 | from django.conf import settings 18 | from django.urls import path, include 19 | from main.admin import admin_site # type: ignore # main/admin.py is entirely ignored 20 | 21 | 22 | handler400 = "main.views.bad_request" 23 | handler500 = "main.views.server_error" 24 | 25 | urlpatterns = [ 26 | path("", include("main.urls")), 27 | path("django-admin/", admin_site.urls), 28 | ] 29 | 30 | # use django-debug-toolbar if installed 31 | if settings.DEBUG: 32 | try: 33 | import debug_toolbar 34 | 35 | urlpatterns += [path("__debug__/", include(debug_toolbar.urls))] 36 | except ImportError: 37 | pass 38 | -------------------------------------------------------------------------------- /web/config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for config project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") 15 | 16 | # patch email sending to retry on error, to work around sporadic connection issues 17 | from django.core.mail import EmailMessage 18 | from smtplib import SMTPException 19 | from time import sleep 20 | 21 | _orig_send = EmailMessage.send 22 | 23 | 24 | def retrying_send(message, *args, **kwargs): 25 | try: 26 | return _orig_send(message, *args, **kwargs) 27 | except (SMTPException, TimeoutError): 28 | sleep(1) 29 | return _orig_send(message, *args, **kwargs) 30 | 31 | 32 | # Mypy doesn't like monkey patching https://github.com/python/mypy/issues/2427 33 | EmailMessage.send = retrying_send # type: ignore 34 | 35 | application = get_wsgi_application() 36 | -------------------------------------------------------------------------------- /web/frontend/components/CollapseTriangle.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 36 | -------------------------------------------------------------------------------- /web/frontend/components/Globals.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 21 | -------------------------------------------------------------------------------- /web/frontend/components/HighlightAnnotation.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 20 | 21 | 33 | -------------------------------------------------------------------------------- /web/frontend/components/LinkInput.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 | 19 | -------------------------------------------------------------------------------- /web/frontend/components/LoadingSpinner.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /web/frontend/components/SideMenu.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | 14 | 27 | -------------------------------------------------------------------------------- /web/frontend/components/SpacePreserver.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 20 | -------------------------------------------------------------------------------- /web/frontend/components/TableOfContents/PlaceHolder.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /web/frontend/components/TheGlobalElisionExpansionButton.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 23 | 24 | 41 | -------------------------------------------------------------------------------- /web/frontend/components/TinyMCEEditor.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 29 | -------------------------------------------------------------------------------- /web/frontend/config/axios.js: -------------------------------------------------------------------------------- 1 | import AxiosConfig from "axios"; 2 | import {get_csrf_token} from 'legacy/lib/helpers'; 3 | 4 | let headers = {"Content-Type": "application/json", 5 | "Accept": "application/json"}; 6 | const csrf_token = get_csrf_token(); 7 | if(csrf_token) headers["X-CSRF-Token"] = csrf_token; 8 | 9 | const Axios = AxiosConfig.create({headers: headers}); 10 | 11 | // Add method override to request 12 | Axios.interceptors.request.use(config => { 13 | const method = config.method.toUpperCase(); 14 | if (["PUT", "DELETE", "PATCH"].includes(method)) { 15 | config.headers = { 16 | ...config.headers, 17 | ["X-HTTP-Method-Override"]: method, 18 | }; 19 | config.method = "post"; 20 | } 21 | return config; 22 | }); 23 | 24 | export default Axios; 25 | -------------------------------------------------------------------------------- /web/frontend/directives/selectionchange.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | const handlers = new WeakMap(); 4 | const prev_in_el = new WeakMap(); 5 | 6 | // Accepts a handler in the form of function(event, selection) 7 | // where selection will be null in the event the user has selected 8 | // something outside of the element on which this directive has been placed. 9 | Vue.directive('selectionchange', { 10 | inserted(el, binding) { 11 | handlers.set(el, function (evt) { 12 | const sel = document.getSelection(); 13 | const in_el = el.contains(sel.anchorNode); 14 | const lost_focus = prev_in_el.get(el) && !in_el; 15 | if (in_el || lost_focus) { 16 | binding.value(evt, lost_focus ? null : sel); 17 | } 18 | prev_in_el.set(el, in_el); 19 | }); 20 | document.addEventListener('selectionchange', handlers.get(el)); 21 | }, 22 | unbind(el, binding) { 23 | document.removeEventListener('selectionchange', handlers.get(el)); 24 | handlers.delete(el); 25 | prev_in_el.delete(el); 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /web/frontend/fonts/AtlasGrotesk-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/AtlasGrotesk-Bold.eot -------------------------------------------------------------------------------- /web/frontend/fonts/AtlasGrotesk-Bold.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/AtlasGrotesk-Bold.svg -------------------------------------------------------------------------------- /web/frontend/fonts/AtlasGrotesk-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/AtlasGrotesk-Bold.ttf -------------------------------------------------------------------------------- /web/frontend/fonts/AtlasGrotesk-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/AtlasGrotesk-Bold.woff -------------------------------------------------------------------------------- /web/frontend/fonts/AtlasGrotesk-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/AtlasGrotesk-Light.eot -------------------------------------------------------------------------------- /web/frontend/fonts/AtlasGrotesk-Light.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/AtlasGrotesk-Light.svg -------------------------------------------------------------------------------- /web/frontend/fonts/AtlasGrotesk-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/AtlasGrotesk-Light.ttf -------------------------------------------------------------------------------- /web/frontend/fonts/AtlasGrotesk-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/AtlasGrotesk-Light.woff -------------------------------------------------------------------------------- /web/frontend/fonts/AtlasGrotesk-Medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/AtlasGrotesk-Medium.eot -------------------------------------------------------------------------------- /web/frontend/fonts/AtlasGrotesk-Medium.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/AtlasGrotesk-Medium.svg -------------------------------------------------------------------------------- /web/frontend/fonts/AtlasGrotesk-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/AtlasGrotesk-Medium.ttf -------------------------------------------------------------------------------- /web/frontend/fonts/AtlasGrotesk-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/AtlasGrotesk-Medium.woff -------------------------------------------------------------------------------- /web/frontend/fonts/AtlasGrotesk-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/AtlasGrotesk-Regular.eot -------------------------------------------------------------------------------- /web/frontend/fonts/AtlasGrotesk-Regular.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/AtlasGrotesk-Regular.svg -------------------------------------------------------------------------------- /web/frontend/fonts/AtlasGrotesk-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/AtlasGrotesk-Regular.ttf -------------------------------------------------------------------------------- /web/frontend/fonts/AtlasGrotesk-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/AtlasGrotesk-Regular.woff -------------------------------------------------------------------------------- /web/frontend/fonts/ChronicleTextG3-Roman.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/ChronicleTextG3-Roman.eot -------------------------------------------------------------------------------- /web/frontend/fonts/ChronicleTextG3-Roman.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/ChronicleTextG3-Roman.svg -------------------------------------------------------------------------------- /web/frontend/fonts/ChronicleTextG3-Roman.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/ChronicleTextG3-Roman.ttf -------------------------------------------------------------------------------- /web/frontend/fonts/ChronicleTextG3-Roman.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/ChronicleTextG3-Roman.woff -------------------------------------------------------------------------------- /web/frontend/fonts/RobotoMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/RobotoMono-Bold.ttf -------------------------------------------------------------------------------- /web/frontend/fonts/tinymce-small.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/tinymce-small.eot -------------------------------------------------------------------------------- /web/frontend/fonts/tinymce-small.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/tinymce-small.ttf -------------------------------------------------------------------------------- /web/frontend/fonts/tinymce-small.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/tinymce-small.woff -------------------------------------------------------------------------------- /web/frontend/fonts/tinymce.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/tinymce.eot -------------------------------------------------------------------------------- /web/frontend/fonts/tinymce.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/tinymce.ttf -------------------------------------------------------------------------------- /web/frontend/fonts/tinymce.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/frontend/fonts/tinymce.woff -------------------------------------------------------------------------------- /web/frontend/legacy/README.txt: -------------------------------------------------------------------------------- 1 | These files were originally served using an earlier Rails asset pipeline (Sprockets) instead of the later Rails asset 2 | pipeline (webpacker). They could be better integrated with the other frontend files. -------------------------------------------------------------------------------- /web/frontend/legacy/lib/ui/content/annotate_modal.js: -------------------------------------------------------------------------------- 1 | import ModalComponent from 'legacy/lib/ui/modal'; 2 | import delegate from 'delegate'; 3 | import {html} from 'es6-string-html-template'; 4 | 5 | delegate(document, '.annotate-casebook', 'click', showAnnotateModal); 6 | 7 | function showAnnotateModal (e) { 8 | new AnnotateModal('clone-modal', e.target, {}); 9 | 10 | e.currentTarget.activeElement.dataset.processing = "true" // override the component destroy events 11 | } 12 | 13 | class AnnotateModal extends ModalComponent { 14 | template () { 15 | return html`` 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /web/frontend/legacy/lib/ui/content/clone_modal.js: -------------------------------------------------------------------------------- 1 | import ModalComponent from 'legacy/lib/ui/modal'; 2 | import delegate from 'delegate'; 3 | import {html} from 'es6-string-html-template'; 4 | 5 | delegate(document, '.clone-casebook', 'click', showCloneModal); 6 | 7 | function showCloneModal (e) { 8 | new CloneModal('clone-modal', e.target, {}); 9 | 10 | e.currentTarget.activeElement.dataset.processing = "true" // override the component destroy events 11 | } 12 | 13 | class CloneModal extends ModalComponent { 14 | template () { 15 | return html`` 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /web/frontend/legacy/lib/ui/content/index.js: -------------------------------------------------------------------------------- 1 | import './dashboard.js'; 2 | import './revise_modal'; 3 | import './clone_modal'; 4 | import './annotate_modal'; 5 | import './export_modal.js'; 6 | -------------------------------------------------------------------------------- /web/frontend/legacy/lib/ui/content/revise_modal.js: -------------------------------------------------------------------------------- 1 | import {html} from 'es6-string-html-template'; 2 | import delegate from 'delegate'; 3 | import ModalComponent from 'legacy/lib/ui/modal'; 4 | 5 | delegate(document, '.create-draft', 'click', showReviseModal); 6 | 7 | function showReviseModal (e) { 8 | new ReviseModal('revise-modal', e.target, {}); 9 | 10 | e.currentTarget.activeElement.dataset.processing = "true" // override the component destroy events 11 | } 12 | 13 | class ReviseModal extends ModalComponent { 14 | 15 | template () { 16 | return html`` 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /web/frontend/legacy/lib/ui/filter-selectize.js: -------------------------------------------------------------------------------- 1 | $(document).ready(e => { 2 | 3 | if($('.view-searches-index, .view-searches-show').length){ 4 | 5 | $('#search_author').on('change', function (e) { 6 | e.target.closest('form').submit(); 7 | }); 8 | 9 | $('#search_school').on('change', function (e) { 10 | e.target.closest('form').submit(); 11 | }); 12 | 13 | $('#search_sort').on('change', function (e) { 14 | e.target.closest('form').submit(); 15 | }); 16 | } 17 | 18 | }); 19 | -------------------------------------------------------------------------------- /web/frontend/legacy/lib/ui/skip-link-focus-fix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * File skip-link-focus-fix.js. 3 | * 4 | * Helps with accessibility for keyboard only users. 5 | * 6 | * Learn more: https://git.io/vWdr2 7 | */ 8 | ( function() { 9 | var isIe = /(trident|msie)/i.test( navigator.userAgent ); 10 | 11 | if ( isIe && document.getElementById && window.addEventListener ) { 12 | window.addEventListener( 'hashchange', function() { 13 | var id = location.hash.substring( 1 ), 14 | element; 15 | 16 | if ( ! ( /^[A-z0-9_-]+$/.test( id ) ) ) { 17 | return; 18 | } 19 | 20 | element = document.getElementById( id ); 21 | 22 | if ( element ) { 23 | if ( ! ( /^(?:a|select|input|button|textarea)$/i.test( element.tagName ) ) ) { 24 | element.tabIndex = -1; 25 | } 26 | 27 | element.focus(); 28 | } 29 | }, false ); 30 | } 31 | } )(); -------------------------------------------------------------------------------- /web/frontend/legacy/polyfills/index.js: -------------------------------------------------------------------------------- 1 | import "@babel/polyfill"; 2 | import 'element-closest'; 3 | import './promises'; 4 | -------------------------------------------------------------------------------- /web/frontend/legacy/polyfills/promises.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird'; 2 | window.Promise = Promise; 3 | Promise.config({ 4 | longStackTraces: true, 5 | warnings: true 6 | }); 7 | -------------------------------------------------------------------------------- /web/frontend/pages/application.js: -------------------------------------------------------------------------------- 1 | // This file is compiled by vue-cli as configured by vue.config.js, as are other files in this directory. 2 | // NOTE: jquery itself is imported using ProvidePlugin 3 | import 'jquery-ui'; 4 | import 'jquery-ujs'; 5 | import 'legacy/polyfills'; 6 | import 'legacy/lib/bootstrap/dropdown'; 7 | import 'legacy/lib/helpers'; 8 | import 'legacy/lib/requests'; 9 | import 'legacy/lib/ui/skip-link-focus-fix'; 10 | import 'legacy/lib/ui/content'; 11 | import 'legacy/lib/ui/filter-selectize'; 12 | -------------------------------------------------------------------------------- /web/frontend/pages/main.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | 3 | // import bootstrap mixins to build semantic styles 4 | @import '~bootstrap-sass/assets/stylesheets/bootstrap'; 5 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/scaffolding"; 6 | @import "~bootstrap-sass/assets/stylesheets/bootstrap/normalize"; 7 | 8 | // shared variables 9 | @import 'font-vars-and-mixins'; 10 | @import 'font-faces'; 11 | @import 'mixins'; 12 | 13 | // global styles 14 | @import 'text'; 15 | @import 'forms'; 16 | @import 'buttons'; 17 | 18 | // shared semantic styles 19 | @import 'layout'; 20 | @import 'header'; 21 | @import 'footer'; 22 | @import 'mockups'; 23 | @import 'spinner'; 24 | @import 'announcement_banner'; 25 | 26 | // view semantic styles 27 | @import 'landing'; 28 | @import 'searches'; 29 | @import 'users'; 30 | @import 'dashboard'; 31 | @import 'casebook'; 32 | @import 'pages'; 33 | @import 'cases'; 34 | @import 'uscode'; 35 | @import 'tinymce_editor'; 36 | -------------------------------------------------------------------------------- /web/frontend/pages/rich_text_editor.js: -------------------------------------------------------------------------------- 1 | import tinymce from 'tinymce/tinymce'; 2 | window.tinymce = tinymce; 3 | import 'tinymce/themes/silver'; 4 | import 'tinymce/icons/default'; 5 | import 'tinymce/plugins/link'; 6 | import 'tinymce/plugins/lists'; 7 | import 'tinymce/plugins/image'; 8 | import 'tinymce/plugins/table'; 9 | import 'tinymce/plugins/code'; 10 | import 'tinymce/plugins/paste'; 11 | import 'tinymce/plugins/media'; 12 | import 'tinymce/plugins/noneditable'; 13 | import {getInitConfig} from '../libs/tinymce_extensions'; 14 | 15 | const ENHANCED = window.ENABLE_MEDIA_UPLOAD; 16 | 17 | function initRichTextEditor(element, code=false) { 18 | // Vue rebuilds the whole dom in between the call to init and tinymce actually doing the init 19 | // so we use a selector here until we use vue to init tinymce 20 | const selector=`${element.type}#${element.id}`; 21 | let config = getInitConfig(selector, ENHANCED, code); 22 | 23 | return tinymce.init(config); 24 | } 25 | 26 | for (const textArea of document.querySelectorAll('.richtext-editor')) 27 | initRichTextEditor(textArea); 28 | 29 | for (const textArea of document.querySelectorAll('.richtext-editor-src')) 30 | initRichTextEditor(textArea, true); 31 | 32 | global.initRichTextEditor = initRichTextEditor; 33 | -------------------------------------------------------------------------------- /web/frontend/pages/table_of_contents.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import VueRouter from 'vue-router'; 3 | Vue.use(VueRouter); 4 | Vue.config.productionTip = process.env.NODE_ENV == "development"; 5 | 6 | import store from "../store/index"; 7 | import "../config/axios"; 8 | 9 | import TheTableOfContents from "../components/TheTableOfContents"; 10 | 11 | document.addEventListener("DOMContentLoaded", () => { 12 | const routes = [ 13 | { path: '/casebooks/:casebook_id/section/:section_id/', component: TheTableOfContents }, 14 | { path: '/casebooks/:casebook_id/resource/:section_id/', component: TheTableOfContents }, 15 | { path: '/casebooks/:casebook_id/', component: TheTableOfContents } 16 | 17 | ]; 18 | 19 | const router = new VueRouter({ 20 | routes, 21 | scrollBehavior: function(to, from, savedPosition) { 22 | if (to.hash) { 23 | return {selector: to.hash}; 24 | } else { 25 | return { x: 0, y: 0 }; 26 | } 27 | }, 28 | mode: 'history' 29 | }); 30 | 31 | const el = document.getElementById("table-of-contents"); 32 | const app = new Vue({ 33 | el, 34 | store, 35 | router, 36 | components: { 37 | TheTableOfContents 38 | } 39 | }); 40 | 41 | window.app = app; 42 | }); 43 | -------------------------------------------------------------------------------- /web/frontend/pages/test.js: -------------------------------------------------------------------------------- 1 | import dragMock from 'drag-mock'; 2 | window.dragMock = dragMock; 3 | -------------------------------------------------------------------------------- /web/frontend/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | import annotations from "./modules/annotations"; 4 | import annotations_ui from "./modules/annotations_ui"; 5 | import footnotes_ui from "./modules/footnotes_ui"; 6 | import resources_ui from "./modules/resources_ui"; 7 | import table_of_contents from "./modules/table_of_contents"; 8 | import createLogger from "vuex/dist/logger"; 9 | import case_search from './modules/case_search'; 10 | import globals from './modules/globals'; 11 | 12 | Vue.use(Vuex); 13 | 14 | const debug = process.env.NODE_ENV == "development"; 15 | 16 | export default new Vuex.Store({ 17 | modules: { 18 | annotations, 19 | annotations_ui, 20 | footnotes_ui, 21 | resources_ui, 22 | table_of_contents, 23 | case_search, 24 | globals 25 | }, 26 | strict: debug, 27 | plugins: debug ? [createLogger()] : [] 28 | }); 29 | -------------------------------------------------------------------------------- /web/frontend/store/modules/footnotes_ui.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | const state = { 4 | all: {} 5 | }; 6 | 7 | const getters = { 8 | getById: state => id => 9 | state.all[id] 10 | }; 11 | 12 | const mutations = { 13 | register: (state, payload) => 14 | Vue.set(state.all, payload.id, payload.annotationIds) 15 | }; 16 | 17 | export default { 18 | namespaced: true, 19 | state, 20 | getters, 21 | mutations 22 | }; 23 | -------------------------------------------------------------------------------- /web/frontend/store/modules/globals.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | const state = { 4 | casebook:null, 5 | section:null, 6 | inAuditMode: false 7 | }; 8 | 9 | const getters = { 10 | casebook: (state) => () => { 11 | return state.casebook; 12 | }, 13 | section: (state) => () => { 14 | return state.section; 15 | }, 16 | inAuditMode: (state) => () => { 17 | return state.inAuditMode; 18 | }, 19 | }; 20 | 21 | const mutations = { 22 | setCasebook: (state,value) => state.casebook = value, 23 | setSection: (state,value) => state.section = value, 24 | setAuditMode: (state,value) => state.inAuditMode = value 25 | }; 26 | 27 | export default { 28 | namespaced: true, 29 | state, 30 | getters, 31 | mutations 32 | }; 33 | -------------------------------------------------------------------------------- /web/frontend/store/modules/resources_ui.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | editable: false 3 | }; 4 | 5 | const getters = { 6 | getEditability: state => 7 | state.editable 8 | }; 9 | 10 | const mutations = { 11 | setEditability: (state, payload) => 12 | state.editable = payload 13 | }; 14 | 15 | export default { 16 | namespaced: true, 17 | state, 18 | getters, 19 | mutations 20 | }; 21 | -------------------------------------------------------------------------------- /web/frontend/styles/announcement_banner.scss: -------------------------------------------------------------------------------- 1 | body { 2 | .announcement-banner { 3 | background-color: $tinted-light-blue; 4 | font-size: 14px; 5 | padding: 10px 15px; 6 | @include make-row(); 7 | 8 | p { 9 | margin: 0; 10 | a { 11 | color: inherit; 12 | } 13 | a.standout-link { 14 | color: $light-blue; 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /web/frontend/styles/buttons.scss: -------------------------------------------------------------------------------- 1 | @mixin btn() 2 | { 3 | @include sans-serif($regular, 13px, 16px); 4 | padding: 14px 18px 12px; 5 | border-width: 0px; 6 | text-decoration: none; 7 | box-shadow: none; 8 | } 9 | 10 | @mixin btn-white() 11 | { 12 | @include button-variant($black, $white, transparent); 13 | font-weight: $bold; 14 | } 15 | 16 | .simple_form input[type=submit] { 17 | @extend .btn; 18 | @extend .btn-primary; 19 | } 20 | 21 | .button_to input[type=submit] { 22 | border: none; 23 | padding: 0; 24 | text-align: left; 25 | } 26 | 27 | a.reset-button { 28 | @extend .btn; 29 | @extend .btn-warning; 30 | } 31 | -------------------------------------------------------------------------------- /web/frontend/styles/cases.scss: -------------------------------------------------------------------------------- 1 | @import 'font-vars-and-mixins'; 2 | 3 | .legal-doc-header { 4 | background-color: $white; 5 | padding: 24px 32px 10px 32px; 6 | @include serif-text($bold, 14px, 18px); 7 | line-height:unset; 8 | } 9 | 10 | .case-header { 11 | background-color: white; 12 | 13 | .title, 14 | .citation, 15 | .decisiondate, 16 | .docketnumber, 17 | .court { 18 | text-align:center; 19 | } 20 | .citation, 21 | .decisiondate, 22 | .docketnumber, 23 | .court { 24 | letter-spacing: 1px; 25 | padding: 4px; 26 | } 27 | .court { 28 | font-size:24px; 29 | } 30 | .citation, 31 | .decisiondate, 32 | .docketnumber { 33 | font-size: 18px; 34 | } 35 | .title { 36 | @include serif-text($bold, 27px, 33px); 37 | padding: 20px 4px 10px 4px; 38 | } 39 | } 40 | 41 | .case-text { 42 | .parties, 43 | .decisiondate, 44 | .docketnumber, 45 | .citations, 46 | .syllabus, 47 | .synopsis, 48 | .court { 49 | display:none; 50 | } 51 | .page-label { 52 | display: none; 53 | } 54 | 55 | aside.footnote > a { 56 | float: left; 57 | } 58 | img { 59 | max-width: 100%; 60 | width: auto; 61 | height: auto; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /web/frontend/styles/ckeditor_styles.scss: -------------------------------------------------------------------------------- 1 | .ck-editor__editable { 2 | height: 200px; 3 | } -------------------------------------------------------------------------------- /web/frontend/styles/font-faces.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | @include load-font($preferred-sans-serif, normal, $bold, '~@/fonts/AtlasGrotesk-Medium'); 3 | } 4 | 5 | @font-face { 6 | @include load-font($preferred-sans-serif, normal, $regular, '~@/fonts/AtlasGrotesk-Regular'); 7 | } 8 | 9 | @font-face { 10 | @include load-font($preferred-sans-serif, normal, $light, '~@/fonts/AtlasGrotesk-Light'); 11 | } 12 | 13 | 14 | @font-face { 15 | @include load-font($preferred-serif, normal, $regular, '~@/fonts/ChronicleTextG3-Roman'); 16 | } 17 | 18 | @font-face { 19 | font-family: $preferred-mono; 20 | font-style: normal; 21 | font-weight: $regular; 22 | src: url('~@/fonts/RobotoMono-Bold.ttf') format('ttf'); 23 | } 24 | -------------------------------------------------------------------------------- /web/frontend/styles/font-vars-and-mixins.scss: -------------------------------------------------------------------------------- 1 | $bold: 700; 2 | $medium: 500; 3 | $regular: 400; 4 | $light: 300; 5 | 6 | @mixin load-font($family, $style, $weight, $path) 7 | { 8 | font-family: $family; 9 | font-style: $style; 10 | font-weight: $weight; 11 | src: url($path + '.eot'); 12 | src: url($path + '.eot') format('embedded-opentype'), 13 | url($path + '.woff') format('woff'), 14 | url($path + '.ttf') format('ttf'), 15 | url($path + '.svg') format('svg'); 16 | } 17 | 18 | @mixin set-font($family, $weight, $size, $height) 19 | { 20 | font-family: $family; 21 | font-weight: $weight; 22 | font-size: $size; 23 | line-height: $height; 24 | } 25 | 26 | @mixin sans-serif($weight, $size, $height) 27 | { 28 | @include set-font($font-family-sans-serif, $weight, $size, $height); 29 | } 30 | 31 | @mixin serif-headline($weight, $size, $height) 32 | { 33 | @include set-font($font-family-headline, $weight, $size, $height); 34 | } 35 | 36 | @mixin serif-text($weight, $size, $height) 37 | { 38 | @include set-font($font-family-serif, $weight, $size, $height); 39 | } 40 | 41 | @mixin monospace($weight, $size, $height) 42 | { 43 | @include set-font($font-family-monospace, $weight, $size, $height); 44 | } 45 | -------------------------------------------------------------------------------- /web/frontend/styles/footer.scss: -------------------------------------------------------------------------------- 1 | main > section:last-child { 2 | padding-bottom: 105px; 3 | } 4 | body { 5 | #main-footer { 6 | @include make-row(); 7 | padding: 40px 0 50px; 8 | 9 | background-color: $light-blue; 10 | color: white; 11 | 12 | a { 13 | @include sans-serif($bold, 14px, 20px); 14 | @include link-color($white); 15 | } 16 | .link { 17 | display: block; 18 | } 19 | .content { 20 | @extend .container; 21 | } 22 | .layout { 23 | @include make-row(); 24 | } 25 | .brand { 26 | background-image: url('~static/images/logo-white.png'); 27 | background-position: left; 28 | height: 26px; 29 | margin: 10px 0; 30 | color: transparent; 31 | } 32 | .link-group { 33 | &:first-child { 34 | @include make-md-column(2); 35 | } 36 | &:nth-child(2) { 37 | @include make-md-column(3); 38 | } 39 | } 40 | .info { 41 | @include make-md-column(3); 42 | @extend .pull-right; 43 | text-align: right; 44 | .copyright { 45 | margin-top: 15px; 46 | @include sans-serif($bold, 10px, 16px); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /web/frontend/styles/mockups.scss: -------------------------------------------------------------------------------- 1 | body > .overlay { 2 | display: none; 3 | position: absolute; 4 | left: 0; 5 | top: 0; 6 | right: 0; 7 | height: 3000px; 8 | background-position: center 0; 9 | background-size: initial; 10 | opacity: 0.3; 11 | z-index: 10; 12 | pointer-events: none; 13 | } 14 | body.view-base-index > .overlay { 15 | // background-image: url('~static/images/mockups/landing.png'); 16 | display: none; 17 | } 18 | 19 | body.view-playlists-edit > .overlay { 20 | // background-image: url('~static/images/mockups/new-casebook.png'); 21 | } 22 | -------------------------------------------------------------------------------- /web/frontend/styles/tinymce_editor.scss: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /web/frontend/styles/uscode.scss: -------------------------------------------------------------------------------- 1 | @import 'font-vars-and-mixins'; 2 | 3 | // US GPO 4 | 5 | h4.subsection-head { 6 | font-weight: bold; 7 | font-size: 22px; 8 | } 9 | 10 | h4.notes-section { 11 | margin-top: 6rem; 12 | font-weight: bold; 13 | font-size: 22px; 14 | } 15 | 16 | h4.paragraph-head { 17 | padding-left: 1rem; 18 | } 19 | 20 | @for $indent from 1 through 5 { 21 | // code to execute on each loop 22 | p.statutory-body-#{$indent}em { 23 | padding-left: #{1+$indent}rem; 24 | } 25 | } 26 | 27 | // Headers 28 | 29 | header.uscode-header { 30 | text-align: center; 31 | font-size: 18px; 32 | font-weight: bold; 33 | .citation { 34 | font-size: 18px; 35 | } 36 | .title { 37 | @include serif-text($bold, 27px, 33px); 38 | } 39 | } 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /web/frontend/styles/vars-and-mixins.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/variables'; 2 | @import '~bootstrap-sass/assets/stylesheets/bootstrap/variables'; 3 | @import '~bootstrap-sass/assets/stylesheets/bootstrap/mixins'; 4 | @import 'styles/font-vars-and-mixins'; 5 | @import 'styles/mixins'; 6 | -------------------------------------------------------------------------------- /web/frontend/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | mocha: true 4 | } 5 | } -------------------------------------------------------------------------------- /web/frontend/test/components/TheAnnotator.test.js: -------------------------------------------------------------------------------- 1 | import { parseHTML } from '../test_helpers'; 2 | 3 | import { mount } from '@vue/test-utils'; 4 | import TheAnnotator from '@/components/TheAnnotator'; 5 | 6 | describe('TheAnnotator', () => { 7 | 8 | describe('contributesToOffsets', () => { 9 | it('returns false when node has the data-exclude-from-offset-calcs property', () => { 10 | const wrapper = mount(TheAnnotator); 11 | const node = parseHTML('
foo
'); 12 | expect(wrapper.vm.contributesToOffsets(node)).toBe(false); 13 | }); 14 | 15 | it('returns false when node is the child of an element having the data-exclude-from-offset-calcs property', () => { 16 | const wrapper = mount(TheAnnotator); 17 | const node = parseHTML('
foo
').childNodes[0]; 18 | expect(wrapper.vm.contributesToOffsets(node)).toBe(false); 19 | }); 20 | 21 | it('returns true when node does not have the data-exclude-from-offset-calcs property', () => { 22 | const wrapper = mount(TheAnnotator); 23 | const node = parseHTML('
foo
'); 24 | expect(wrapper.vm.contributesToOffsets(node)).toBe(true); 25 | }); 26 | }); 27 | 28 | }); 29 | -------------------------------------------------------------------------------- /web/frontend/test/libs/example_tocs/colin-miller-best-evidence.txt: -------------------------------------------------------------------------------- 1 | Notices. ii 2 | About the Author iv 3 | About CALI eLangdell Press. v 4 | Table of Contents. vi 5 | Preface. vii 6 | Best Evidence Rule Chapter 1 7 | Introductory Note. 1 8 | I. Historical Origins of the Best Evidence Rule. 1 9 | II. Article X: The Modern Best Evidence Rule. 3 10 | A. Rule 1002: The Rule’s Scope. 3 11 | B. Rule 1001: Defining the Relevant Terms. 8 12 | C. Rule 1003: The Duplicate Exception.. 13 13 | D. Rule 1004: Excusing Nonproduction of Originals. 16 14 | E. Rule 1005: Public Records. 22 15 | F. Rule 1006: Summaries. 24 16 | G. Rule 1007: Admissions. 26 17 | H. Rule 1008: Functions of the Court and Jury. 28 18 | I. The Best Evidence Framework. 30 19 | J. Best Evidence Pleadings. 31 -------------------------------------------------------------------------------- /web/frontend/test/libs/example_tocs/hatfield-ethics-tax-lawyering-3rd.txt: -------------------------------------------------------------------------------- 1 | About the Author iii 2 | Notices. iv 3 | About CALI eLangdell Press. vi 4 | Table of Contents. vii 5 | 1. Introducing Legal Ethics for Tax Lawyers. 1 6 | 1.1. Ethics for Lawyers 1 7 | Notes and Questions. 2 8 | 1.2. The Duty to the Tax System.. 3 9 | Notes and Questions. 3 10 | 1.3. Sharing the Profession with Non-Lawyers. 3 11 | Grace v. Allen.. 4 12 | Notes and Questions. 8 13 | 2. Regulating Tax Lawyering. 11 14 | 2.1. Regulating Tax Lawyering through the IRC.. 11 15 | IRC § 7206. Fraud and false statements. 12 16 | Notes and Questions. 12 17 | § 6694. Understatement of taxpayer's liability by tax return preparer. 14 18 | Notes and Questions. 19 19 | 2.2. Regulating Tax Lawyering through Circular 230. 21 20 | Notes and Questions. 32 21 | WASHBURN v. SHAPIRO... 35 22 | 2.3. Regulating Tax Lawyering through Malpractice Standards. 36 23 | 3. Ethical Problems for Tax Lawyers. 39 24 | 3.1. Tax Opinions and Tax Shelters. 39 25 | Notes and Questions. 43 26 | 3.2. Mistakes. 43 27 | Notes and Questions. 45 28 | 3.3. Working with IRS Lawyers and Other Employees 46 29 | § 10.51 Incompetence and disreputable conduct. 48 30 | Internal Revenue Manual 4.1.1.7.6.1 - Badges of Practitioner Abuse (05-20-2005) 49 31 | From 1991-2 C.B. 1137: 50 32 | Notes and Questions. 51 -------------------------------------------------------------------------------- /web/frontend/test/libs/example_tocs/levin-civil-procedure-pleading.txt: -------------------------------------------------------------------------------- 1 | 1.    Preface. 3 2 | 2.    Rule 8. General Rules of Pleading. 5 3 | 3.    Rule 9. Pleading Special Matters 6 4 | 4.    Conley v. Gibson. 6 5 | 5.    Swierkiewicz v. Sorema N.A. 9 6 | 6.    Rule 10. Form of Pleadings 15 7 | 7.    Bell Atlantic Corp. v. Twombly. 15 8 | 8.    Ashcroft v. Iqbal 31 9 | 9.    Kregler v. City of New York. 45 10 | 10. Complaint 1. 56 11 | 11. Complaint 2. 64 -------------------------------------------------------------------------------- /web/frontend/test/mocha_setup.js: -------------------------------------------------------------------------------- 1 | require("jsdom-global")(); 2 | 3 | global.expect = require("expect"); 4 | global.DOMParser = window.DOMParser; 5 | 6 | // https://github.com/vuejs/vue-test-utils/issues/936#issuecomment-415386167 7 | window.Date = Date; 8 | global.FRONTEND_URLS = { 9 | search_sources: [], 10 | search_using: [], 11 | new_from_outline: [], 12 | }; 13 | -------------------------------------------------------------------------------- /web/frontend/test/test_helpers.js: -------------------------------------------------------------------------------- 1 | export const parseHTML = (html) => { 2 | const parser = new DOMParser(); 3 | const doc = parser.parseFromString(html, "text/html"); 4 | return doc.body.children[0]; 5 | }; 6 | 7 | export const createText = (s) => document.createTextNode(s); 8 | 9 | export const removeVueScopedCSSAttributes = (element) => { 10 | let attrNodes = document.evaluate('//*/attribute::*[starts-with(name(), "data-v-")]', element, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null); 11 | 12 | let attrNode; 13 | while((attrNode = attrNodes.iterateNext())) attrNode.ownerElement.removeAttributeNode(attrNode); 14 | return element; 15 | }; 16 | -------------------------------------------------------------------------------- /web/main/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/main/__init__.py -------------------------------------------------------------------------------- /web/main/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MainConfig(AppConfig): 5 | name = "main" 6 | -------------------------------------------------------------------------------- /web/main/authenticator.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.backends import ModelBackend 2 | 3 | 4 | class NormalizingAuthenticator(ModelBackend): 5 | def authenticate(self, request, username=None, password=None): 6 | try: 7 | [user, domain] = username.split("@") 8 | email = "@".join([user, domain.lower()]) 9 | sup = super().authenticate(request, username=email, password=password) 10 | return sup 11 | except Exception: 12 | return None 13 | -------------------------------------------------------------------------------- /web/main/hashers.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | from django.contrib.auth.hashers import PBKDF2PasswordHasher 4 | 5 | 6 | class PBKDF2WrappedRailsPasswordHasher(PBKDF2PasswordHasher): 7 | """ 8 | Legacy password hasher -- see https://docs.djangoproject.com/en/2.2/topics/auth/passwords/#password-upgrading-without-requiring-a-login 9 | 10 | Rails-era passwords are stored as a salted password hashed 20 times with sha512. This legacy hasher wraps those 11 | hashes in our standard PBKDF2 hasher. 12 | """ 13 | 14 | algorithm = "pbkdf2_wrapped_rails" 15 | 16 | def encode_rails_hash(self, digest, salt, iterations=None): 17 | """Used by the database migration.""" 18 | return super().encode(digest, salt, iterations) 19 | 20 | def encode(self, password, salt, iterations=None): 21 | """Used for checking passwords on login.""" 22 | digest = password + salt 23 | for _ in range(20): 24 | digest = hashlib.sha512(digest.encode("utf8")).hexdigest() 25 | return super().encode(digest, salt, iterations) 26 | -------------------------------------------------------------------------------- /web/main/migrations/0002_auto_20201021_1444.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.16 on 2020-10-21 14:44 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0001_squashed_0072_auto_20201015_1225'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='case', 15 | name='jurisdiction_id', 16 | field=models.IntegerField(blank=True, null=True), 17 | ), 18 | migrations.AddField( 19 | model_name='case', 20 | name='jurisdiction_slug', 21 | field=models.CharField(blank=True, max_length=20), 22 | ), 23 | migrations.AddField( 24 | model_name='historicalcase', 25 | name='jurisdiction_id', 26 | field=models.IntegerField(blank=True, null=True), 27 | ), 28 | migrations.AddField( 29 | model_name='historicalcase', 30 | name='jurisdiction_slug', 31 | field=models.CharField(blank=True, max_length=20), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /web/main/migrations/0004_auto_20210405_1927.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.19 on 2021-04-05 19:27 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0003_alternate_legal_docs'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='legaldocumentsource', 15 | name='enabled', 16 | ), 17 | migrations.AddField( 18 | model_name='legaldocumentsource', 19 | name='priority', 20 | field=models.IntegerField(null=True), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /web/main/migrations/0006_uscodeindex_repealed.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.20 on 2021-04-16 00:42 2 | 3 | from django.db import migrations, models 4 | 5 | def check_repealed(apps, schema_editor): 6 | USCodeIndex = apps.get_model('main', 'USCodeIndex') 7 | for ind in USCodeIndex.objects.all(): 8 | ind.repealed = ind.title.startswith("Repealed") 9 | ind.save() 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('main', '0005_auto_20210414_1240'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name='uscodeindex', 20 | name='repealed', 21 | field=models.BooleanField(null=True), 22 | ), 23 | migrations.RunPython(check_repealed) 24 | ] 25 | -------------------------------------------------------------------------------- /web/main/migrations/0007_savedimage.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.21 on 2021-05-05 13:48 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('main', '0006_uscodeindex_repealed'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='SavedImage', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('created_at', models.DateTimeField(auto_now_add=True)), 19 | ('updated_at', models.DateTimeField(auto_now=True)), 20 | ('name', models.CharField(max_length=255)), 21 | ('file_name', models.CharField(max_length=255)), 22 | ('alt_text', models.CharField(max_length=1000)), 23 | ('uploaded_by', models.ForeignKey(on_delete=models.DO_NOTHING, related_name='saved_images', to=settings.AUTH_USER_MODEL)), 24 | ], 25 | options={ 26 | 'abstract': False, 27 | }, 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /web/main/migrations/0008_auto_20210511_1533.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.21 on 2021-05-11 15:33 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0007_savedimage'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='savedimage', 15 | name='alt_text', 16 | ), 17 | migrations.AlterField( 18 | model_name='savedimage', 19 | name='file_name', 20 | field=models.CharField(blank=True, max_length=255, null=True), 21 | ), 22 | migrations.AlterField( 23 | model_name='savedimage', 24 | name='name', 25 | field=models.CharField(blank=True, max_length=255, null=True), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /web/main/migrations/0009_auto_20210512_0217.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.21 on 2021-05-12 02:17 2 | 3 | from django.db import migrations, models 4 | import main.storages 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('main', '0008_auto_20210511_1533'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='savedimage', 16 | name='file_name', 17 | ), 18 | migrations.AddField( 19 | model_name='savedimage', 20 | name='image', 21 | field=models.FileField(default=None, storage=main.storages.S3Storage(access_key='accesskey', bucket_name='h2o.images', endpoint_url='http://minio:9000', secret_key='secretkey'), upload_to=''), 22 | preserve_default=False, 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /web/main/migrations/0010_auto_20210512_0257.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.21 on 2021-05-12 02:57 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0009_auto_20210512_0217'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='savedimage', 15 | name='external_id', 16 | field=models.UUIDField(default=None, unique=True), 17 | preserve_default=False, 18 | ), 19 | migrations.AddIndex( 20 | model_name='savedimage', 21 | index=models.Index(fields=['external_id'], name='main_savedi_externa_97e5f9_idx'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /web/main/migrations/0011_auto_20210805_1723.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.24 on 2021-08-05 17:23 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0010_auto_20210512_0257'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='contentnode', 15 | name='headnote_doc_class', 16 | field=models.CharField(blank=True, max_length=40, null=True), 17 | ), 18 | migrations.AddField( 19 | model_name='historicalcontentnode', 20 | name='headnote_doc_class', 21 | field=models.CharField(blank=True, max_length=40, null=True), 22 | ), 23 | migrations.AddField( 24 | model_name='historicaltextblock', 25 | name='doc_class', 26 | field=models.CharField(blank=True, max_length=40, null=True), 27 | ), 28 | migrations.AddField( 29 | model_name='textblock', 30 | name='doc_class', 31 | field=models.CharField(blank=True, max_length=40, null=True), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /web/main/migrations/0013_casebookfollow.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.24 on 2021-10-04 16:02 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('main', '0012_auto_20210914_0057'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='CasebookFollow', 17 | fields=[ 18 | ('id', models.BigAutoField(primary_key=True, serialize=False)), 19 | ('created_at', models.DateTimeField(auto_now_add=True)), 20 | ('updated_at', models.DateTimeField(auto_now=True)), 21 | ('casebook', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='main.Casebook')), 22 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 23 | ], 24 | options={ 25 | 'unique_together': {('user', 'casebook')}, 26 | }, 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /web/main/migrations/0014_auto_20211007_1248.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.24 on 2021-10-07 12:48 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0013_casebookfollow'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='casebook', 15 | name='export_fails', 16 | field=models.IntegerField(default=0), 17 | ), 18 | migrations.AddField( 19 | model_name='historicalcasebook', 20 | name='export_fails', 21 | field=models.IntegerField(default=0), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /web/main/migrations/0015_auto_20211014_0041.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.24 on 2021-10-14 00:41 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0014_auto_20211007_1248'), 10 | ] 11 | 12 | operations = [ 13 | migrations.DeleteModel( 14 | name='Case', 15 | ), 16 | migrations.DeleteModel( 17 | name='HistoricalCase', 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /web/main/migrations/0016_auto_20211025_2147.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.24 on 2021-10-25 21:47 2 | 3 | import django.contrib.postgres.fields 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('main', '0015_auto_20211014_0041'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='contentnode', 16 | name='display_ordinals', 17 | field=django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(), default=list, size=None), 18 | ), 19 | migrations.AddField( 20 | model_name='contentnode', 21 | name='does_display_ordinals', 22 | field=models.BooleanField(default=True), 23 | ), 24 | migrations.AddField( 25 | model_name='historicalcontentnode', 26 | name='display_ordinals', 27 | field=django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(), default=list, size=None), 28 | ), 29 | migrations.AddField( 30 | model_name='historicalcontentnode', 31 | name='does_display_ordinals', 32 | field=models.BooleanField(default=True), 33 | ), 34 | ] 35 | -------------------------------------------------------------------------------- /web/main/migrations/0017_auto_20211130_2114.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.24 on 2021-11-30 19:58 2 | 3 | from django.db import migrations 4 | from django.db.models import F 5 | 6 | 7 | def populate_display_ordinals(apps, schema_editor): 8 | ContentNode = apps.get_model('main', 'ContentNode') 9 | if not ContentNode.objects.exclude(display_ordinals=[]).exists(): 10 | ContentNode.objects.all().update(display_ordinals=F('ordinals')) 11 | 12 | 13 | def unset_display_ordinals(apps, schema_editor): 14 | ContentNode = apps.get_model('main', 'ContentNode') 15 | ContentNode.objects.all().update(display_ordinals=[]) 16 | 17 | 18 | class Migration(migrations.Migration): 19 | 20 | dependencies = [ 21 | ('main', '0016_auto_20211025_2147'), 22 | ] 23 | 24 | operations = [ 25 | migrations.RunPython(populate_display_ordinals, unset_display_ordinals), 26 | ] 27 | -------------------------------------------------------------------------------- /web/main/migrations/0019_livesettings.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.26 on 2022-01-10 20:23 2 | 3 | from django.db import migrations, models 4 | 5 | class Migration(migrations.Migration): 6 | 7 | dependencies = [ 8 | ('main', '0018_auto_20211130_2236'), 9 | ] 10 | 11 | operations = [ 12 | migrations.CreateModel( 13 | name='LiveSettings', 14 | fields=[ 15 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 16 | ('prevent_exports', models.BooleanField(default=False)), 17 | ], 18 | ), 19 | ] 20 | 21 | -------------------------------------------------------------------------------- /web/main/migrations/0020_auto_20220209_1732.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.27 on 2022-02-09 17:32 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0019_livesettings'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='livesettings', 15 | name='export_average_rate', 16 | field=models.IntegerField(default=0), 17 | ), 18 | migrations.AddField( 19 | model_name='livesettings', 20 | name='export_last_minute_updated', 21 | field=models.IntegerField(default=0), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /web/main/migrations/0021_auto_20220527_1714.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.28 on 2022-05-27 17:14 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0020_auto_20220209_1732'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='livesettings', 15 | options={'verbose_name_plural': 'Live settings'}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /web/main/migrations/0024_drop_raw_headnote.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.14 on 2022-07-06 17:32 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0023_drop_legacy_annotation_fields'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='contentnode', 15 | name='raw_headnote', 16 | ), 17 | migrations.RemoveField( 18 | model_name='historicalcontentnode', 19 | name='raw_headnote', 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /web/main/migrations/0025_drop_created_via_import.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.14 on 2022-07-06 19:33 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0024_drop_raw_headnote'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='historicaltextblock', 15 | name='created_via_import', 16 | ), 17 | migrations.RemoveField( 18 | model_name='textblock', 19 | name='created_via_import', 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /web/main/migrations/0026_drop_public_on_textblock.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.14 on 2022-07-06 20:39 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0025_drop_created_via_import'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='historicaltextblock', 15 | name='public', 16 | ), 17 | migrations.RemoveField( 18 | model_name='textblock', 19 | name='public', 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /web/main/migrations/0027_drop_legacy_tables.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.14 on 2022-07-07 18:08 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0026_drop_public_on_textblock'), 10 | ] 11 | 12 | operations = [ 13 | migrations.DeleteModel( 14 | name='CkeditorAsset', 15 | ), 16 | migrations.DeleteModel( 17 | name='ContentImage', 18 | ), 19 | migrations.DeleteModel( 20 | name='Media', 21 | ), 22 | migrations.DeleteModel( 23 | name='MediaType', 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /web/main/migrations/0028_fulltextsearchindex.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.14 on 2022-07-14 17:36 2 | 3 | import django.contrib.postgres.search 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('main', '0027_drop_legacy_tables'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='FullTextSearchIndex', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('result_id', models.IntegerField()), 19 | ('document', django.contrib.postgres.search.SearchVectorField()), 20 | ('metadata', models.JSONField()), 21 | ('category', models.CharField(max_length=255)), 22 | ], 23 | options={ 24 | 'db_table': 'fts_internal_search_view', 25 | 'managed': False, 26 | }, 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /web/main/migrations/0029_auto_20220714_1747.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.14 on 2022-07-14 17:47 2 | 3 | from django.db import migrations, models 4 | import main.models 5 | import main.storages 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('main', '0028_fulltextsearchindex'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='casebook', 17 | name='cover_image', 18 | field=models.FileField(blank=True, null=True, storage=main.storages.S3Storage(access_key='accesskey', bucket_name='h2o.images', endpoint_url='http://opencasebook.minio.test:9000', secret_key='secretkey'), upload_to=main.models.cover_image_path), 19 | ), 20 | migrations.AddField( 21 | model_name='historicalcasebook', 22 | name='cover_image', 23 | field=models.TextField(blank=True, max_length=100, null=True), 24 | ), 25 | migrations.AlterField( 26 | model_name='savedimage', 27 | name='image', 28 | field=models.FileField(storage=main.storages.S3Storage(access_key='accesskey', bucket_name='h2o.images', endpoint_url='http://opencasebook.minio.test:9000', secret_key='secretkey'), upload_to=''), 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /web/main/migrations/0030_reading_length.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.14 on 2022-07-13 19:42 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0029_auto_20220714_1747'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='contentnode', 15 | name='reading_length', 16 | field=models.IntegerField(null=True), 17 | ), 18 | migrations.AddField( 19 | model_name='historicalcontentnode', 20 | name='reading_length', 21 | field=models.IntegerField(null=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /web/main/migrations/0031_casebook_description_20220719_1550.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.14 on 2022-07-19 15:50 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('main', '0030_reading_length'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='casebook', 16 | name='description', 17 | field=models.TextField(blank=True, null=True, validators=[django.core.validators.MaxLengthValidator(750)]), 18 | ), 19 | migrations.AddField( 20 | model_name='historicalcasebook', 21 | name='description', 22 | field=models.TextField(blank=True, null=True, validators=[django.core.validators.MaxLengthValidator(750)]), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /web/main/migrations/0032_setting_for_html_export.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.14 on 2022-08-01 14:42 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0031_casebook_description_20220719_1550'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='livesettings', 15 | name='enable_printable_html_export', 16 | field=models.BooleanField(default=False, help_text='Enable the view to export entire casebooks as HTML'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /web/main/migrations/0035_user_details.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.14 on 2022-08-18 05:37 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0034_add_instructional_content_field'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='user', 15 | name='personal_site', 16 | field=models.CharField(blank=True, max_length=255), 17 | ), 18 | migrations.AddField( 19 | model_name='user', 20 | name='pronouns', 21 | field=models.CharField(blank=True, max_length=63), 22 | ), 23 | migrations.AddField( 24 | model_name='user', 25 | name='short_bio', 26 | field=models.CharField(blank=True, max_length=511), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /web/main/migrations/0036_instructional_material_help_text.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.15 on 2022-08-22 14:16 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0035_user_details'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='contentnode', 15 | name='is_instructional_material', 16 | field=models.BooleanField(default=False, help_text='This content should only be made available on the front end to verified professors'), 17 | ), 18 | migrations.AlterField( 19 | model_name='historicalcontentnode', 20 | name='is_instructional_material', 21 | field=models.BooleanField(default=False, help_text='This content should only be made available on the front end to verified professors'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /web/main/migrations/0039_drop_printable_livesetting.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.15 on 2022-10-11 15:23 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0038_initial_institution_data'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='livesettings', 15 | name='enable_printable_html_export', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /web/main/migrations/0040_drop_link_content_type_field.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.16 on 2022-11-14 20:59 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0039_drop_printable_livesetting'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='historicallink', 15 | name='content_type', 16 | ), 17 | migrations.RemoveField( 18 | model_name='link', 19 | name='content_type', 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /web/main/migrations/0043_add_listed_publicly_field.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.18 on 2023-04-18 15:03 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0042_legaldocument_indexes'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='casebook', 15 | name='listed_publicly', 16 | field=models.BooleanField(db_index=True, default=True, help_text='Whether the casebook, when published, is available in public listings such as H2O search or search engine indexes.'), 17 | ), 18 | migrations.AddField( 19 | model_name='historicalcasebook', 20 | name='listed_publicly', 21 | field=models.BooleanField(db_index=True, default=True, help_text='Whether the casebook, when published, is available in public listings such as H2O search or search engine indexes.'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /web/main/migrations/0044_add_user_groups.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.18 on 2023-04-18 18:17 2 | 3 | from django.db import migrations 4 | from django.contrib.auth.models import Group 5 | 6 | from main.models import User 7 | 8 | 9 | def create_user_groups(apps, schema_editor): 10 | Group.objects.get_or_create(name='Professor') 11 | Group.objects.get_or_create(name='Student') 12 | Group.objects.get_or_create(name='Librarian') 13 | Group.objects.get_or_create(name='Other') 14 | 15 | def populate_professor_group(apps, schema_editor): 16 | professor = Group.objects.get(name='Professor') 17 | 18 | for prof in User.objects.filter(verified_professor=True): 19 | prof.groups.add(professor) 20 | 21 | class Migration(migrations.Migration): 22 | 23 | dependencies = [ 24 | ('main', '0043_add_listed_publicly_field'), 25 | ] 26 | 27 | operations = [ 28 | migrations.RunPython(create_user_groups), 29 | migrations.RunPython(populate_professor_group), 30 | ] 31 | -------------------------------------------------------------------------------- /web/main/migrations/0045_add_courtlistener_api_source.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.18 on 2023-04-12 20:18 2 | 3 | from django.db import migrations 4 | from datetime import date 5 | 6 | 7 | def add_courtlistener(apps, schema_editor): 8 | """Add CourtListener as a first-order type in the UI""" 9 | LegalDocumentSource = apps.get_model("main", "LegalDocumentSource") 10 | if not LegalDocumentSource.objects.filter(name="CourtListener"): 11 | LegalDocumentSource.objects.create( 12 | name="CourtListener", 13 | date_added=date.today(), 14 | last_updated=date.today(), 15 | active=False, 16 | priority=3, 17 | search_class="CourtListener", 18 | short_description="CourtListener searches millions of opinions across hundreds of jurisdictions.", 19 | ) 20 | 21 | 22 | class Migration(migrations.Migration): 23 | 24 | dependencies = [ 25 | ("main", "0044_add_user_groups"), 26 | ] 27 | 28 | operations = [ 29 | migrations.RunPython(add_courtlistener), 30 | ] 31 | -------------------------------------------------------------------------------- /web/main/migrations/0046_auto_20240516_1322.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.25 on 2024-05-16 13:22 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0045_add_courtlistener_api_source'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='contentnode', 15 | name='ali_licensed', 16 | field=models.BooleanField(default=False), 17 | ), 18 | migrations.AddField( 19 | model_name='historicalcontentnode', 20 | name='ali_licensed', 21 | field=models.BooleanField(default=False), 22 | ) 23 | ] -------------------------------------------------------------------------------- /web/main/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/main/migrations/__init__.py -------------------------------------------------------------------------------- /web/main/templates/400.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block page_title %}400 Bad Request{% endblock %} 4 | 5 | {% block mainContent %} 6 |
7 |
8 |

Bad Request

9 |

The browser (or proxy) sent a request that this server could not understand. If you require assistance, please contact us.

10 |
11 |
12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /web/main/templates/403.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block page_title %}403 Forbidden{% endblock %} 4 | 5 | {% block mainContent %} 6 |
7 |
8 |

Permission Denied

9 |

You don't appear to have access to the requested resource. If you believe you reached this page in error, please contact us.

10 |
11 |
12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /web/main/templates/403_csrf.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block page_title %}403 Login Request Failed{% endblock %} 4 | 5 | {% block mainContent %} 6 |
7 |
8 |

Login Request Failed

9 |

Your attempt to log in failed. If you believe you reached this page in error, please contact us.

10 |
11 |
12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /web/main/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block page_title %}404 Page Not Found{% endblock %} 4 | 5 | {% block mainContent %} 6 |
7 |
8 |

Oops!

9 |

We can't seem to find the page you are looking for.

10 |

To report a broken link, please contact us.

11 |
12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /web/main/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block page_title %}500 Internal Server Error{% endblock %} 4 | 5 | {% block mainContent %} 6 |
7 |
8 |

Oops!

9 |

There’s been an internal server error preparing this page, but don't worry, we'll fix it.

10 |

Please contact us and let us know how you found yourself here. Thanks!

11 |
12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /web/main/templates/admin/change_form_with_richeditor.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/change_form.html" %} 2 | {% load render_bundle from webpack_loader %} 3 | 4 | {% block footer %} 5 | {{ block.super }} 6 | {% render_bundle 'chunk-common' %} 7 | {% render_bundle 'rich_text_editor' %} 8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /web/main/templates/admin/h2o_index.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/index.html" %} 2 | 3 | {% block userlinks %} 4 | 5 | Usage / 6 | {{ block.super }} 7 | 8 | {% endblock userlinks %} -------------------------------------------------------------------------------- /web/main/templates/admin/input_filter.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 |

{% blocktrans with filter_title=title %} By {{ filter_title }} {% endblocktrans %}

4 | 21 | -------------------------------------------------------------------------------- /web/main/templates/admin/main/casebook/change_form.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/change_form_with_richeditor.html" %} 2 | -------------------------------------------------------------------------------- /web/main/templates/admin/main/legaldocument/change_form.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/change_form_with_richeditor.html" %} 2 | {% load admin_urls %} 3 | 4 | {% block after_field_sets %} 5 | {{ block.super }} 6 |
7 |
8 |
{{ original.has_newer_version }}
9 |
10 |
11 |
12 |
13 |
{{ original.related_annotations.count }}
14 |
15 |
16 | {% endblock %} 17 | 18 | {% block submit_buttons_bottom %} 19 | {{ block.super }} 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /web/main/templates/admin/main/link/change_form.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/change_form.html" %} 2 | {% load admin_urls %} 3 | 4 | -------------------------------------------------------------------------------- /web/main/templates/admin/main/resource/change_form.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/change_form_with_richeditor.html" %} 2 | {% load admin_urls %} 3 | 4 | {% block after_related_objects %} 5 | {{ block.super }} 6 |
7 |

Resource

8 |
9 |
10 | 11 |
{{ original.resource_id }}
12 |
13 |
14 |
15 |
16 | 17 |
{{ original.resource_type }}
18 |
19 |
20 |
21 | {% with 'admin:main_'|add:original.resource_type|add:'_change'|lower as change_url %} 22 | 23 | View in admin 24 | 25 | {% endwith %} 26 |
27 |
28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /web/main/templates/admin/main/section/change_form.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/change_form_with_richeditor.html" %} 2 | -------------------------------------------------------------------------------- /web/main/templates/admin/main/textblock/change_form.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/change_form_with_richeditor.html" %} 2 | {% load admin_urls %} 3 | 4 | {% block after_field_sets %} 5 | {{ block.super }} 6 |
7 |
8 |
{{ original.related_annotations.count }}
9 |
10 |
11 | {% endblock %} 12 | 13 | 14 | -------------------------------------------------------------------------------- /web/main/templates/archived_casebooks.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends 'base.html' %} 3 | 4 | {% block custom_skip_target %}{% endblock %} 5 | 6 | {% block mainContent %} 7 | {# This appears to be solely for spacing
#} 8 |
9 |

Main Content

10 |
11 |

My Archived Casebooks

12 | 13 |
14 |

Archived Casebooks do not appear in search results and are not viewable by other users.

15 | {% include "includes/content_browser.html" with content=user.archived_casebooks %} 16 |
17 |
18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /web/main/templates/casebook_outline_edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% if edit_mode or clone_section_targets %} 4 | {% load render_bundle from webpack_loader %} 5 | {% load crispy_forms_tags %} 6 | {% endif %} 7 | 8 | {% block page_title %} {% if mode %}{{mode}} | {% endif %} {{casebook.title}} {% if section %}: {{ section.title }} {% endif %} {% endblock %} 9 | 10 | {% if editing %} 11 | {% block extra_foot %}{% render_bundle 'rich_text_editor' %}{% endblock %} 12 | {% endif %} 13 | 14 | {% block banner %} 15 | {% include 'includes/preview_banner.html' %} 16 | {% endblock %} 17 | 18 | {% block mainContent %} 19 | {% include 'includes/casebook_page_tabs.html' %} 20 | 21 |
22 | 27 | /> 28 |
29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /web/main/templates/casebook_page_credits.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block page_title %} {% if mode %}{{mode}} | {% endif %} {{casebook.title}} {% if section %}: {{ section.title }} {% endif %} {% endblock %} 4 | 5 | 6 | {% block banner %} 7 | {% include 'includes/preview_banner.html' %} 8 | {% endblock %} 9 | 10 | {% block mainContent %} 11 | {% include 'includes/casebook_page_tabs.html' %} 12 |
13 |
14 |
15 |
16 | {% include 'includes/credits.html' %} 17 |
18 |
19 |
20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /web/main/templates/casebook_page_related.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block page_title %} {% if mode %}{{mode}} | {% endif %} {{casebook.title}} {% if section %}: {{ section.title }} {% endif %} {% endblock %} 4 | 5 | 6 | {% block banner %} 7 | {% include 'includes/preview_banner.html' %} 8 | {% endblock %} 9 | 10 | {% block mainContent %} 11 | {% include 'includes/casebook_page_tabs.html' %} 12 | 13 | 21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /web/main/templates/casebook_page_search.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block page_title %} {% if mode %}{{mode}} | {% endif %} {{casebook.title}} {% if section %}: {{ section.title }} {% endif %} {% endblock %} 4 | 5 | 6 | {% block banner %} 7 | {% include 'includes/preview_banner.html' %} 8 | {% endblock %} 9 | 10 | {% block mainContent %} 11 | {% include 'includes/casebook_page_tabs.html' %} 12 | 22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /web/main/templates/export/as_printable_html/credits.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | Acknowledgments 4 |

5 |

6 | Some materials included in this export came from the following casebooks. 7 |

8 | 22 | 23 |
24 | -------------------------------------------------------------------------------- /web/main/templates/export/as_printable_html/tbd.html: -------------------------------------------------------------------------------- 1 | {% if node.type == 'section' %} 2 |
{{ node.ordinal_string }}
3 |

{{ node.title }}

4 | {% if node.subtitle %} 5 |
{{ node.subtitle }}
6 | {% endif %} 7 | {% if node.headnote %} 8 |
{{ node.headnote_for_export }}
9 | {% endif %} 10 |

TBD

11 | -------------------------------------------------------------------------------- /web/main/templates/export/credits.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | Acknowledgments 5 |
6 |
7 | Some materials included in this export came from the following casebooks. 8 |
9 | 23 | 24 |
25 | -------------------------------------------------------------------------------- /web/main/templates/export/section.html: -------------------------------------------------------------------------------- 1 | 2 | {% include "export/node.html" with index=0 node=node %} 3 | {% with is_child=True %} 4 | {% for child in children %} 5 | {% include "export/node.html" with index=forloop.counter node=child %} 6 | {% endfor %} 7 | {% endwith %} 8 | 9 | -------------------------------------------------------------------------------- /web/main/templates/export/tbd.html: -------------------------------------------------------------------------------- 1 | {% if node.type == 'section' %} 2 |
{{ node.ordinal_string }}
3 |
{{ node.title }}
4 | {% if node.subtitle %} 5 |
{{ node.subtitle }}
6 | {% endif %} 7 | {% if node.headnote %} 8 |
{{ node.headnote_for_export }}
9 | {% endif %} 10 |

TBD

11 | -------------------------------------------------------------------------------- /web/main/templates/export_error.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block page_title %}Export error{% endblock %} 4 | 5 | {% block mainContent %} 6 |
7 |
8 |

Export error

9 |

We're having trouble exporting this casebook currently. Our team has been notified and we're looking into the issue.

10 |

For now, you can return to the casebook

11 |
12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /web/main/templates/includes/analytics.html: -------------------------------------------------------------------------------- 1 | {% if USE_ANALYTICS %} 2 | {# Ensure that MATOMO_SITE_ID and MATOMO_SITE_URL are both set for this environment #} 3 | 18 | {% endif %} 19 | -------------------------------------------------------------------------------- /web/main/templates/includes/announcement_banner.html: -------------------------------------------------------------------------------- 1 |
2 |

New! H2O now has access to new and up-to-date cases via 3 | CourtListener and the Caselaw Access Project. 4 | Click here for more info. 5 |

6 |
-------------------------------------------------------------------------------- /web/main/templates/includes/bodies/case.html: -------------------------------------------------------------------------------- 1 |
2 | {% include 'includes/case_header.html' with case=section.resource %} 3 | 4 |
5 | -------------------------------------------------------------------------------- /web/main/templates/includes/bodies/empty.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/main/templates/includes/bodies/empty.html -------------------------------------------------------------------------------- /web/main/templates/includes/bodies/legal_doc.html: -------------------------------------------------------------------------------- 1 |
2 | {% include section.resource.header_template with legal_doc=section.resource %} 3 | 4 |
5 | -------------------------------------------------------------------------------- /web/main/templates/includes/bodies/link.html: -------------------------------------------------------------------------------- 1 |
2 | 13 |
14 | -------------------------------------------------------------------------------- /web/main/templates/includes/bodies/text_block.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /web/main/templates/includes/breadcrumbs.html: -------------------------------------------------------------------------------- 1 | {% load call_method %} 2 | {% load humanize_minutes %} 3 | {% if content.ordinals %} 4 | 28 | {% endif %} 29 | -------------------------------------------------------------------------------- /web/main/templates/includes/case_header.html: -------------------------------------------------------------------------------- 1 |
2 | {% if case.prefer_meta_header %} 3 | {% if case.court_name %}
{{ case.court_name }}
{% endif %} 4 |

{% firstof section.title case.get_name %}

5 | {% if case.citations %}
{{case.cite_string}}
{% endif %} 6 | {% if case.docket_number %}
{{ case.docket_number }}
{% endif %} 7 | {% if case.decision_date %}
{{ case.decision_date }}
{% endif %} 8 | {% else %} 9 |

{% firstof section.title case.get_name %}

10 | {% endif %} 11 |
12 | -------------------------------------------------------------------------------- /web/main/templates/includes/casebook_copyright_notice.html: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /web/main/templates/includes/casebook_page_tabs.html: -------------------------------------------------------------------------------- 1 |
7 | {% if tabs %} 8 |
9 |
10 |
11 | {% for name, link, is_active_tab in tabs %} 12 | {% if is_active_tab %} 13 | {{name}} 14 | {% else %} 15 | {{name}} 16 | {% endif %} 17 | {% endfor %} 18 |
19 |
20 |
21 | {% endif %} 22 |
23 | -------------------------------------------------------------------------------- /web/main/templates/includes/collaborators.html: -------------------------------------------------------------------------------- 1 | {% for user in content.primary_authors %} 2 |
3 | {{ user.display_name }}{% if not forloop.last %}, {% endif %} 4 |
5 | {% endfor %} 6 | -------------------------------------------------------------------------------- /web/main/templates/includes/featured_casebook.html: -------------------------------------------------------------------------------- 1 | {% if casebook %} 2 | 23 | {% else %} 24 | 25 | {% endif %} 26 | 27 | -------------------------------------------------------------------------------- /web/main/templates/includes/footer.html: -------------------------------------------------------------------------------- 1 |
2 |
H2O
3 |
4 | 14 |
15 | 16 | 17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /web/main/templates/includes/headnote.html: -------------------------------------------------------------------------------- 1 | {% if content.headnote or content.subtitle %} 2 |
3 |

4 | {{ content.headnote | safe }} 5 |

6 |
7 | {% else %} 8 |
9 | {% endif %} 10 | -------------------------------------------------------------------------------- /web/main/templates/includes/legal_doc_sources/cap_header.html: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /web/main/templates/includes/legal_doc_sources/court_listener_header.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/main/templates/includes/legal_doc_sources/empty_header.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | -------------------------------------------------------------------------------- /web/main/templates/includes/legal_doc_sources/gpo_header.html: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /web/main/templates/includes/page_buttons.html: -------------------------------------------------------------------------------- 1 | {% with prev_node_url=previous_and_next_urls.0 next_node_url=previous_and_next_urls.1 %} 2 | 3 | 20 | 21 | {% endwith %} 22 | -------------------------------------------------------------------------------- /web/main/templates/includes/reading_mode_toc_item.html: -------------------------------------------------------------------------------- 1 | {% load reading_mode_toc_item %} 2 | 3 | 4 |
    5 | {% for child in toc %} 6 |
  1. 7 | 8 | {{ child.ordinal_string|default:"—" }} 9 | 10 | 11 | {{ child.title }} 12 | 13 | {% if child.children and child.id == top_level_node.id %} 14 | {% reading_mode_toc_item child.children casebook top_level_node %} 15 | {% endif %} 16 | 17 |
  2. 18 | {% endfor %} 19 |
-------------------------------------------------------------------------------- /web/main/templates/includes/sentry.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/main/templates/includes/table-of-contents.html: -------------------------------------------------------------------------------- 1 |
2 | 6 | 7 |
8 | -------------------------------------------------------------------------------- /web/main/templates/legal_doc.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block page_title %}{{ legal_doc.get_name }} | Legal Documents{% endblock %} 4 | 5 | {% block mainContent %} 6 |
7 |
8 |
9 |
10 | {% include legal_doc.header_template %} 11 |
12 | 13 |
14 |
15 |
16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /web/main/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load crispy_forms_tags %} 3 | {% block page_title %}Log in{% endblock %} 4 | 5 | {% block mainContent %} 6 |
7 |
8 |

9 | Sign in to your account 10 |

11 |
12 |
13 |
14 |
15 |
16 |
17 | {% crispy form %} 18 | 19 |

By signing in to your account, you agree to our Terms of Service.

20 |
21 |
22 |
23 |

Need help?

24 | 29 |
30 |
31 |
32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /web/main/templates/registration/password_change_done.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load crispy_forms_tags %} 3 | {% block page_title %}Password changed{% endblock %} 4 | 5 | {% block mainContent %} 6 |
7 |
8 |
9 |

10 | Password changed 11 |

12 |
13 |
14 | 19 |
20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /web/main/templates/registration/password_change_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load crispy_forms_tags %} 3 | {% block page_title %}Change password{% endblock %} 4 | 5 | {% block mainContent %} 6 |
7 |
8 |

9 | Change your password 10 |

11 |
12 |
13 |
14 |
15 |
16 |
17 | {% crispy form %} 18 | 19 |
20 |
21 |
22 |
23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /web/main/templates/registration/password_reset_complete.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load crispy_forms_tags %} 3 | {% block page_title %}Password changed{% endblock %} 4 | 5 | {% block mainContent %} 6 |
7 |
8 |
9 |

10 | Password changed 11 |

12 |
13 |
14 | 19 |
20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /web/main/templates/registration/password_reset_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load crispy_forms_tags %} 3 | {% block page_title %}Password reset confirmation{% endblock %} 4 | 5 | {% block mainContent %} 6 | {% if not validlink %}
{% endif %} 7 |
8 |
9 |

10 | Password reset confirmation 11 |

12 |
13 |
14 | 29 | {% if not validlink %}
{% endif %} 30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /web/main/templates/registration/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load crispy_forms_tags %} 3 | {% block page_title %}Password changed{% endblock %} 4 | 5 | {% block mainContent %} 6 |
7 |
8 |
9 |

10 | Password reset 11 |

12 |
13 |
14 | 20 |
21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /web/main/templates/registration/password_reset_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load crispy_forms_tags %} 3 | {% block page_title %}Password reset{% endblock %} 4 | 5 | {% block mainContent %} 6 |
7 |
8 |

9 | Password reset 10 |

11 |
12 |
13 |
14 |
15 |

Forgotten your password? Enter your email address below, and we'll email instructions for setting a new one.

16 |
17 |
18 | {% crispy form %} 19 | 20 |
21 |
22 |
23 |
24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /web/main/templates/registration/sign_up.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load crispy_forms_tags %} 3 | {% block page_title %}Sign up{% endblock %} 4 | 5 | {% block mainContent %} 6 |
7 |
8 |

9 | Sign up for an account 10 |

11 |
12 |
13 |
14 |
15 |
16 | {% crispy form %} 17 |
18 |
19 |
20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /web/main/templates/robots.txt: -------------------------------------------------------------------------------- 1 | User-Agent: * 2 | Disallow: /*/export 3 | 4 | {% for casebook in excluded_casebooks %} 5 | Disallow: {{ casebook.get_absolute_url }}* 6 | {% endfor %} -------------------------------------------------------------------------------- /web/main/templates/user_edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load crispy_forms_tags %} 3 | {% block page_title %}Your Profile{% endblock %} 4 | 5 | {% block mainContent %} 6 |

Edit your profile

7 |
8 |
9 |
10 | {% crispy form %} 11 |
12 |
13 |
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /web/main/templatetags/call_method.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | register = template.Library() 4 | 5 | 6 | @register.simple_tag 7 | def call_method(obj, method, *args, **kwargs): 8 | """ 9 | Call a method on an object and return the result. Example: 10 | 11 | {% call_method casebook has_collaborator request.user as has_collaborator %} 12 | 13 | Useful for migrating Rails templates that have assignment statements at the top. 14 | """ 15 | return getattr(obj, method)(*args, **kwargs) 16 | -------------------------------------------------------------------------------- /web/main/templatetags/current_query_string.py: -------------------------------------------------------------------------------- 1 | import urllib.parse 2 | from django import template 3 | 4 | register = template.Library() 5 | 6 | 7 | @register.simple_tag(takes_context=True) 8 | def current_query_string(context, **kwargs): 9 | """ 10 | Given {% current_query_string page=1 q='' %}, return the current query string but with page and q values changed. 11 | """ 12 | return urllib.parse.urlencode(dict(context["request"].GET, **kwargs), doseq=True) 13 | -------------------------------------------------------------------------------- /web/main/templatetags/export_node_html.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | from main.export import annotated_content_for_export 4 | from main.models import ContentNode 5 | 6 | register = template.Library() 7 | 8 | 9 | @register.simple_tag 10 | def export_node_html(node: ContentNode, export_options: dict = None, *args, **kwargs): 11 | return annotated_content_for_export(node, export_options) 12 | -------------------------------------------------------------------------------- /web/main/templatetags/featured_casebook.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from django import template 3 | from main.models import Casebook 4 | 5 | register = template.Library() 6 | 7 | 8 | @register.inclusion_tag("includes/featured_casebook.html") 9 | def featured_casebook( 10 | id: int, 11 | title: Optional[str] = None, 12 | authors: Optional[str] = None, 13 | cover_image: Optional[str] = None, 14 | ): 15 | """ 16 | Render a casebook on the Featured Casebooks page, optionally overriding metadata on the casebook object itself 17 | """ 18 | casebook = Casebook.objects.filter(id=id).first() 19 | 20 | if not casebook: 21 | return {"error": f"Casebook ID {id} was not found in this environment"} 22 | 23 | if not casebook.is_public: 24 | return {"error": f"Casebook ID {id} is not publicly viewable"} 25 | 26 | return { 27 | "casebook": casebook, 28 | "title": title or casebook.title, 29 | "authors": ( 30 | [{"display_name": authors}] 31 | if authors 32 | else [author for author in casebook.primary_authors if author.verified_professor] 33 | ), 34 | "cover_image": cover_image, 35 | "error": None, 36 | } 37 | -------------------------------------------------------------------------------- /web/main/templatetags/humanize_minutes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django's 'humanize' tags don't include methods for humanizing 3 | durations of time (only timestamps). 4 | These methods do that. 5 | """ 6 | 7 | from django import template 8 | 9 | register = template.Library() 10 | 11 | 12 | @register.filter 13 | def humanize_minutes(minutes): 14 | # eesh this is messy with a lot of special cases 15 | if minutes < 1: 16 | return "less than a minute" 17 | if minutes < 2: 18 | return "1 minute" 19 | if minutes < 45: 20 | return f"{minutes:0.0f} minutes" 21 | halves = (minutes * 2) // 60 22 | if halves == 1: 23 | return "half an hour" 24 | if halves == 2: 25 | return "1 hour" 26 | if halves % 2: 27 | return f"{halves / 2:0.1f} hours" 28 | return f"{halves / 2:0.0f} hours" 29 | -------------------------------------------------------------------------------- /web/main/templatetags/reading_mode_toc_item.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from main.models import Casebook, ContentNode 3 | 4 | register = template.Library() 5 | 6 | 7 | @register.inclusion_tag("includes/reading_mode_toc_item.html") 8 | def reading_mode_toc_item(toc: dict, casebook: Casebook, top_level_node: ContentNode): 9 | """ 10 | Render one level of node in the reading mode TOC 11 | """ 12 | 13 | return {"toc": toc, "casebook": casebook, "top_level_node": top_level_node} 14 | -------------------------------------------------------------------------------- /web/main/templatetags/short_page_range.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | register = template.Library() 4 | 5 | 6 | @register.filter 7 | def short_page_range(page, padding=2): 8 | """ 9 | Return just the page numbers we want to display from a Django Page object returned by a Paginator. 10 | E.g., assuming we are on page 10 of 20: 11 | {% for num in page|short_page_range %}num, {% endfor %} 12 | Will output: 13 | 1, 2, ..., 8, 9, 10, 11, 12, ..., 19, 20 14 | """ 15 | paginator = page.paginator 16 | show_ellipsis = True 17 | for i in paginator.page_range: 18 | if i <= 2 or abs(i - page.number) <= padding or i >= paginator.num_pages - 1: 19 | yield i 20 | show_ellipsis = True 21 | elif show_ellipsis: 22 | show_ellipsis = False 23 | yield "..." 24 | -------------------------------------------------------------------------------- /web/main/templatetags/string_strip.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | register = template.Library() 4 | 5 | 6 | @register.filter 7 | def string_strip(content: str, string_from: str) -> str: 8 | "Remove a substring." 9 | return content.replace(string_from, "") 10 | -------------------------------------------------------------------------------- /web/main/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/main/test/__init__.py -------------------------------------------------------------------------------- /web/main/test/functional/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/main/test/functional/__init__.py -------------------------------------------------------------------------------- /web/main/test/functional/fixtures/contentcollaborators.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "main.contentcollaborator", 4 | "pk": 1, 5 | "fields": { 6 | "created_at": "2022-10-13T20:15:29.544", 7 | "updated_at": "2022-10-13T20:15:29.544", 8 | "has_attribution": true, 9 | "can_edit": true, 10 | "user": 1, 11 | "casebook": 1 12 | } 13 | }, 14 | { 15 | "model": "main.contentcollaborator", 16 | "pk": 2, 17 | "fields": { 18 | "created_at": "2022-10-13T20:15:29.544", 19 | "updated_at": "2022-10-13T20:15:29.544", 20 | "has_attribution": true, 21 | "can_edit": true, 22 | "user": 2, 23 | "casebook": 2 24 | } 25 | }, 26 | { 27 | "model": "main.contentcollaborator", 28 | "pk": 3, 29 | "fields": { 30 | "created_at": "2022-10-13T20:15:29.544", 31 | "updated_at": "2022-10-13T20:15:29.544", 32 | "has_attribution": true, 33 | "can_edit": true, 34 | "user": 3, 35 | "casebook": 1 36 | } 37 | }, 38 | { 39 | "model": "main.contentcollaborator", 40 | "pk": 4, 41 | "fields": { 42 | "created_at": "2023-02-21T15:35:50.241", 43 | "updated_at": "2023-02-21T15:35:50.241", 44 | "has_attribution": true, 45 | "can_edit": true, 46 | "user": 1, 47 | "casebook": 3 48 | } 49 | } 50 | ] 51 | -------------------------------------------------------------------------------- /web/main/test/test_publishing.py: -------------------------------------------------------------------------------- 1 | from main.models import Casebook 2 | 3 | from django.urls import reverse 4 | 5 | 6 | def test_publish_new_casebook(private_casebook, client): 7 | """Newly-composed (private, never-published) casebooks, when published, become public""" 8 | assert private_casebook.state == Casebook.LifeCycle.PRIVATELY_EDITING.value 9 | 10 | response = client.post( 11 | reverse("publish", args=[private_casebook]), 12 | as_user=private_casebook.testing_editor, 13 | ) 14 | assert response.status_code == 200 15 | private_casebook.refresh_from_db() 16 | assert private_casebook.is_public 17 | 18 | 19 | def test_publish_draft(casebook, client): 20 | """Drafts of already-published casebooks, when published, replace their parent.""" 21 | 22 | assert casebook.is_public 23 | draft = casebook.make_draft() 24 | draft.title = "new title" 25 | draft.save() 26 | assert casebook.title != draft.title 27 | 28 | response = client.post( 29 | reverse("publish", args=[draft]), 30 | as_user=casebook.testing_editor, 31 | ) 32 | assert response.status_code == 200 33 | casebook.refresh_from_db() 34 | assert casebook.is_public 35 | assert casebook.title == draft.title 36 | -------------------------------------------------------------------------------- /web/main/test/test_templatetags.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from main.templatetags.featured_casebook import featured_casebook 4 | 5 | 6 | def test_featured_casebook(full_casebook): 7 | """Featuring a casebook should return information about it, unless overridden""" 8 | assert featured_casebook(full_casebook.id)["title"] == full_casebook.title 9 | assert featured_casebook(full_casebook.id, title="Fake title")["title"] == "Fake title" 10 | assert ( 11 | featured_casebook(full_casebook.id, authors="Fake authors")["authors"][0]["display_name"] 12 | == "Fake authors" 13 | ) 14 | assert featured_casebook(full_casebook.id)["error"] is None 15 | 16 | 17 | def test_featured_casebook_private(private_casebook): 18 | """A private casebook should return only an error message""" 19 | assert "not publicly viewable" in featured_casebook(private_casebook.id)["error"] 20 | 21 | 22 | @pytest.mark.django_db 23 | def test_featured_casebook_missing(): 24 | """Attempting to retrieve a non-existent casebook should return a friendly error message""" 25 | assert "not found" in featured_casebook(-1)["error"] 26 | -------------------------------------------------------------------------------- /web/main/test/views.py: -------------------------------------------------------------------------------- 1 | from django.core.exceptions import PermissionDenied, SuspiciousOperation 2 | from django.http import Http404 3 | from django.views.decorators.csrf import requires_csrf_token 4 | 5 | 6 | def raise_400(request): 7 | raise SuspiciousOperation("Fishy") 8 | 9 | 10 | def raise_403(request): 11 | raise PermissionDenied 12 | 13 | 14 | @requires_csrf_token 15 | def raise_403_csrf(request): 16 | pass # pragma: no cover 17 | 18 | 19 | def raise_404(request): 20 | raise Http404("Does not exist") 21 | 22 | 23 | def raise_500(request): 24 | raise Exception("Oops") 25 | -------------------------------------------------------------------------------- /web/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == "__main__": 21 | main() 22 | -------------------------------------------------------------------------------- /web/reporting/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/reporting/__init__.py -------------------------------------------------------------------------------- /web/reporting/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ReportingConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "reporting" 7 | -------------------------------------------------------------------------------- /web/reporting/migrations/0002_plural_name_reporting.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.14 on 2022-08-08 14:40 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('reporting', '0001_add_reporting_proxy_models'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='casebookseries', 15 | options={'ordering': ('created_at',), 'verbose_name_plural': 'Casebooks in series'}, 16 | ), 17 | migrations.AlterModelOptions( 18 | name='casebookseriesprof', 19 | options={'ordering': ('created_at',), 'verbose_name_plural': 'Casebooks in series by professors'}, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /web/reporting/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/reporting/migrations/__init__.py -------------------------------------------------------------------------------- /web/reporting/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path("stats/", views.matomo_stats, name="matomo-stats"), 7 | path( 8 | "time-series/professor-casebooks", 9 | views.professor_casebook_timeseries, 10 | name="reporting-professor-casebook-timeseries", 11 | ), 12 | path( 13 | "time-series/casebooks", 14 | views.casebook_timeseries, 15 | name="reporting-casebook-timeseries", 16 | ), 17 | path( 18 | "time-series/professors-published", 19 | views.professor_cumulative_publication_timeseries, 20 | name="reporting-professors-published-timeseries", 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /web/static/as_printable_html/print.js: -------------------------------------------------------------------------------- 1 | import { Previewer } from "./pagedjs.js"; 2 | 3 | console.log("Starting pagination"); 4 | 5 | const paged = new Previewer(); 6 | 7 | paged 8 | .preview( 9 | document.querySelector("main"), 10 | [window.css], 11 | document.querySelector("#pdf-output") 12 | ) 13 | .then((flow) => { 14 | console.log("Rendered", flow.total, "pages."); 15 | document.querySelector("main").remove(); 16 | document.querySelector("#pdf-output").style.visibility = "visible"; 17 | }); 18 | -------------------------------------------------------------------------------- /web/static/as_printable_html/uscode.css: -------------------------------------------------------------------------------- 1 | h4.subsection-head { 2 | font-weight: bold; 3 | font-size: 22px; 4 | } 5 | 6 | h4.notes-section { 7 | margin-top: 6rem; 8 | font-weight: bold; 9 | font-size: 22px; 10 | } 11 | 12 | h4.paragraph-head { 13 | padding-left: 1rem; 14 | } 15 | 16 | header.uscode-header { 17 | text-align: center; 18 | font-size: 18px; 19 | font-weight: bold; 20 | } 21 | header.uscode-header .citation { 22 | font-size: 18px; 23 | } 24 | -------------------------------------------------------------------------------- /web/static/dist/fonts/AtlasGrotesk-Light.bcfd7cf6.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/dist/fonts/AtlasGrotesk-Light.bcfd7cf6.woff -------------------------------------------------------------------------------- /web/static/dist/fonts/AtlasGrotesk-Light.be1c731f.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/dist/fonts/AtlasGrotesk-Light.be1c731f.eot -------------------------------------------------------------------------------- /web/static/dist/fonts/AtlasGrotesk-Light.e69872f9.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/dist/fonts/AtlasGrotesk-Light.e69872f9.ttf -------------------------------------------------------------------------------- /web/static/dist/fonts/AtlasGrotesk-Medium.14791ebe.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/dist/fonts/AtlasGrotesk-Medium.14791ebe.eot -------------------------------------------------------------------------------- /web/static/dist/fonts/AtlasGrotesk-Medium.83bc0a62.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/dist/fonts/AtlasGrotesk-Medium.83bc0a62.woff -------------------------------------------------------------------------------- /web/static/dist/fonts/AtlasGrotesk-Medium.d9cc5003.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/dist/fonts/AtlasGrotesk-Medium.d9cc5003.ttf -------------------------------------------------------------------------------- /web/static/dist/fonts/AtlasGrotesk-Regular.1f499573.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/dist/fonts/AtlasGrotesk-Regular.1f499573.ttf -------------------------------------------------------------------------------- /web/static/dist/fonts/AtlasGrotesk-Regular.94001d71.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/dist/fonts/AtlasGrotesk-Regular.94001d71.eot -------------------------------------------------------------------------------- /web/static/dist/fonts/AtlasGrotesk-Regular.d2997fb4.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/dist/fonts/AtlasGrotesk-Regular.d2997fb4.woff -------------------------------------------------------------------------------- /web/static/dist/fonts/ChronicleTextG3-Roman.6a453768.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/dist/fonts/ChronicleTextG3-Roman.6a453768.ttf -------------------------------------------------------------------------------- /web/static/dist/fonts/ChronicleTextG3-Roman.f77c0515.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/dist/fonts/ChronicleTextG3-Roman.f77c0515.woff -------------------------------------------------------------------------------- /web/static/dist/fonts/ChronicleTextG3-Roman.f9c467a3.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/dist/fonts/ChronicleTextG3-Roman.f9c467a3.eot -------------------------------------------------------------------------------- /web/static/dist/fonts/RobotoMono-Bold.c0c4a337.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/dist/fonts/RobotoMono-Bold.c0c4a337.ttf -------------------------------------------------------------------------------- /web/static/dist/fonts/glyphicons-halflings-regular.448c34a5.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/dist/fonts/glyphicons-halflings-regular.448c34a5.woff2 -------------------------------------------------------------------------------- /web/static/dist/fonts/glyphicons-halflings-regular.e18bbf61.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/dist/fonts/glyphicons-halflings-regular.e18bbf61.ttf -------------------------------------------------------------------------------- /web/static/dist/fonts/glyphicons-halflings-regular.f4769f9b.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/dist/fonts/glyphicons-halflings-regular.f4769f9b.eot -------------------------------------------------------------------------------- /web/static/dist/fonts/glyphicons-halflings-regular.fa277232.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/dist/fonts/glyphicons-halflings-regular.fa277232.woff -------------------------------------------------------------------------------- /web/static/dist/img/AtlasGrotesk-Light.219d1463.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/dist/img/AtlasGrotesk-Light.219d1463.svg -------------------------------------------------------------------------------- /web/static/dist/img/AtlasGrotesk-Medium.3e829f26.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/dist/img/AtlasGrotesk-Medium.3e829f26.svg -------------------------------------------------------------------------------- /web/static/dist/img/AtlasGrotesk-Regular.26133821.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/dist/img/AtlasGrotesk-Regular.26133821.svg -------------------------------------------------------------------------------- /web/static/dist/img/ChronicleTextG3-Roman.0e3646cf.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/dist/img/ChronicleTextG3-Roman.0e3646cf.svg -------------------------------------------------------------------------------- /web/static/dist/img/Link.148d855f.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /web/static/dist/img/add-casebook.ff004159.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /web/static/dist/img/add-material.d109215f.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | add-material 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /web/static/dist/img/add-section.a7b07d92.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | add-section 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /web/static/dist/img/cancel-icon.101f571f.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | button/cancel 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /web/static/dist/img/edit-icon.d38c8fff.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | edit 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /web/static/dist/img/expand-arrow.6bf1cea3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /web/static/dist/img/export-html.a6acb1d4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /web/static/dist/img/external-link-icon.3e1fd22b.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /web/static/dist/img/follow.2e7d7ca7.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 16 | 17 | -------------------------------------------------------------------------------- /web/static/dist/img/landing-demo.769625d8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/dist/img/landing-demo.769625d8.png -------------------------------------------------------------------------------- /web/static/dist/img/link-go.4122dfcb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /web/static/dist/img/lock.0ee492f3.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/static/dist/img/logo-icon-white-on-blue.f2cf816b.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/static/dist/img/next_page.13726d8a.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /web/static/dist/img/prev_page.8adf0763.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /web/static/dist/img/search-icon.8c383980.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /web/static/dist/img/take-notes-icon.26352bcb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /web/static/dist/js/main.8272c3ff.js: -------------------------------------------------------------------------------- 1 | (function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/static/dist/",n(n.s=2)})({2:function(e,t,n){e.exports=n("cd89")},cd89:function(e,t,n){}}); 2 | //# sourceMappingURL=main.8272c3ff.js.map -------------------------------------------------------------------------------- /web/static/fonts/AtlasGrotesk-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/fonts/AtlasGrotesk-Bold.woff2 -------------------------------------------------------------------------------- /web/static/fonts/AtlasGrotesk-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/fonts/AtlasGrotesk-Regular.woff2 -------------------------------------------------------------------------------- /web/static/fonts/ChronicleTextG3-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/fonts/ChronicleTextG3-Bold.woff2 -------------------------------------------------------------------------------- /web/static/fonts/ChronicleTextG3-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/fonts/ChronicleTextG3-Italic.woff2 -------------------------------------------------------------------------------- /web/static/fonts/ChronicleTextG3-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/fonts/ChronicleTextG3-Regular.woff2 -------------------------------------------------------------------------------- /web/static/fonts/LibreCaslonText-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/fonts/LibreCaslonText-Bold.woff2 -------------------------------------------------------------------------------- /web/static/fonts/LibreCaslonText-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/fonts/LibreCaslonText-Italic.woff2 -------------------------------------------------------------------------------- /web/static/fonts/LibreCaslonText-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/fonts/LibreCaslonText-Regular.woff2 -------------------------------------------------------------------------------- /web/static/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/.keep -------------------------------------------------------------------------------- /web/static/images/Link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /web/static/images/add-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/add-icon.png -------------------------------------------------------------------------------- /web/static/images/arrow-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/arrow-search.png -------------------------------------------------------------------------------- /web/static/images/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/close.png -------------------------------------------------------------------------------- /web/static/images/endorsers/cohen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/endorsers/cohen.png -------------------------------------------------------------------------------- /web/static/images/endorsers/fried.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/endorsers/fried.png -------------------------------------------------------------------------------- /web/static/images/endorsers/fried2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/endorsers/fried2.png -------------------------------------------------------------------------------- /web/static/images/endorsers/modirzadeh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/endorsers/modirzadeh.png -------------------------------------------------------------------------------- /web/static/images/endorsers/quinn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/endorsers/quinn.png -------------------------------------------------------------------------------- /web/static/images/endorsers/suk-gersen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/endorsers/suk-gersen.png -------------------------------------------------------------------------------- /web/static/images/endorsers/zittrain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/endorsers/zittrain.png -------------------------------------------------------------------------------- /web/static/images/expand-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /web/static/images/external-link-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /web/static/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/favicon.ico -------------------------------------------------------------------------------- /web/static/images/h20-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/h20-logo.png -------------------------------------------------------------------------------- /web/static/images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/icons.png -------------------------------------------------------------------------------- /web/static/images/landing-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/landing-demo.png -------------------------------------------------------------------------------- /web/static/images/logo-icon-blue-on-yellow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/static/images/logo-icon-white-on-blue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/static/images/logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/logo-white.png -------------------------------------------------------------------------------- /web/static/images/mockups/landing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/mockups/landing.png -------------------------------------------------------------------------------- /web/static/images/mockups/new-casebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/mockups/new-casebook.png -------------------------------------------------------------------------------- /web/static/images/quickbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/quickbar.png -------------------------------------------------------------------------------- /web/static/images/repeat_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/repeat_bg.png -------------------------------------------------------------------------------- /web/static/images/school-logos/berkeley.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/school-logos/berkeley.png -------------------------------------------------------------------------------- /web/static/images/school-logos/harvard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/school-logos/harvard.png -------------------------------------------------------------------------------- /web/static/images/school-logos/michigan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/school-logos/michigan.png -------------------------------------------------------------------------------- /web/static/images/school-logos/penn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/school-logos/penn.png -------------------------------------------------------------------------------- /web/static/images/school-logos/stanford.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/school-logos/stanford.png -------------------------------------------------------------------------------- /web/static/images/school-logos/yale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/school-logos/yale.png -------------------------------------------------------------------------------- /web/static/images/search-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /web/static/images/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/search.png -------------------------------------------------------------------------------- /web/static/images/take-notes-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /web/static/images/tinymce_icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/tinymce_icons.png -------------------------------------------------------------------------------- /web/static/images/transparent.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/transparent.gif -------------------------------------------------------------------------------- /web/static/images/ui/casebook/add-casebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /web/static/images/ui/casebook/add-material.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | add-material 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /web/static/images/ui/casebook/add-section.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | add-section 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /web/static/images/ui/casebook/cancel-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | button/cancel 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /web/static/images/ui/casebook/edit-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | edit 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /web/static/images/ui/casebook/export-html.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /web/static/images/ui/casebook/follow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 16 | 17 | -------------------------------------------------------------------------------- /web/static/images/ui/casebook/link-go.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /web/static/images/ui/casebook/lock.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/static/images/ui/casebook/next_page.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /web/static/images/ui/casebook/prev_page.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /web/static/images/ui/edit/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/ui/edit/add.png -------------------------------------------------------------------------------- /web/static/images/ui/edit/elide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/ui/edit/elide.png -------------------------------------------------------------------------------- /web/static/images/ui/verified-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/ui/verified-large.png -------------------------------------------------------------------------------- /web/static/images/ui/verified.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/images/ui/verified.png -------------------------------------------------------------------------------- /web/static/tinymce_skin/README.txt: -------------------------------------------------------------------------------- 1 | This directory was manually copied from tinymce-5.1.4 by this command: 2 | 3 | cp -r node_modules/tinymce/skins/ui/oxide static/tinymce_skin 4 | 5 | The docs suggest that this manual copy could be avoided using webpack file-loader, but this did not immediately work 6 | when I tried it: 7 | 8 | https://www.tiny.cloud/docs/advanced/usage-with-module-loaders/#webpackfile-loader -------------------------------------------------------------------------------- /web/static/tinymce_skin/content.mobile.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | .tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection { 8 | /* Note: this file is used inside the content, so isn't part of theming */ 9 | background-color: green; 10 | display: inline-block; 11 | opacity: 0.5; 12 | position: absolute; 13 | } 14 | body { 15 | -webkit-text-size-adjust: none; 16 | } 17 | body img { 18 | /* this is related to the content margin */ 19 | max-width: 96vw; 20 | } 21 | body table img { 22 | max-width: 95%; 23 | } 24 | body { 25 | font-family: sans-serif; 26 | } 27 | table { 28 | border-collapse: collapse; 29 | } 30 | -------------------------------------------------------------------------------- /web/static/tinymce_skin/content.mobile.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | .tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{background-color:green;display:inline-block;opacity:.5;position:absolute}body{-webkit-text-size-adjust:none}body img{max-width:96vw}body table img{max-width:95%}body{font-family:sans-serif}table{border-collapse:collapse} 8 | /*# sourceMappingURL=content.mobile.min.css.map */ 9 | -------------------------------------------------------------------------------- /web/static/tinymce_skin/fonts/tinymce-mobile.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/static/tinymce_skin/fonts/tinymce-mobile.woff -------------------------------------------------------------------------------- /web/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/test/__init__.py -------------------------------------------------------------------------------- /web/test/files/export/export-casebook-no-annotations.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/test/files/export/export-casebook-no-annotations.docx -------------------------------------------------------------------------------- /web/test/files/export/export-casebook-with-annotations.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/test/files/export/export-casebook-with-annotations.docx -------------------------------------------------------------------------------- /web/test/files/export/export-resource-no-annotations.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/test/files/export/export-resource-no-annotations.docx -------------------------------------------------------------------------------- /web/test/files/export/export-resource-with-annotations.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/test/files/export/export-resource-with-annotations.docx -------------------------------------------------------------------------------- /web/test/files/export/export-section-no-annotations.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/test/files/export/export-section-no-annotations.docx -------------------------------------------------------------------------------- /web/test/files/export/export-section-with-annotations.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harvard-lil/h2o/c67c4761179b03c1245fcbf7a560ac698a8f8704/web/test/files/export/export-section-with-annotations.docx --------------------------------------------------------------------------------