├── .devdata └── .gitkeep ├── tests ├── __init__.py ├── unit │ ├── __init__.py │ ├── via │ │ ├── __init__.py │ │ ├── views │ │ │ ├── __init__.py │ │ │ ├── api │ │ │ │ ├── __init__.py │ │ │ │ ├── transcript_test.py │ │ │ │ └── youtube_test.py │ │ │ ├── conftest.py │ │ │ ├── webargs_test.py │ │ │ └── proxy_test.py │ │ ├── services │ │ │ ├── __init__.py │ │ │ └── fixtures │ │ │ │ ├── __init__.py │ │ │ │ ├── google_403_not_shared.json │ │ │ │ ├── google_404_file_not_found.json │ │ │ │ ├── google_403_malicious.json │ │ │ │ └── google_403_rate_limited.json │ │ ├── requests_tools │ │ │ └── __init__.py │ │ ├── assets_test.py │ │ ├── sentry_filters_test.py │ │ ├── tweens_test.py │ │ ├── exceptions_test.py │ │ └── app_test.py │ ├── matchers.py │ └── conftest.py ├── common │ ├── __init__.py │ └── requests_exceptions.py ├── functional │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── views │ │ │ └── __init__.py │ │ ├── view_status_test.py │ │ └── view_pdf_test.py │ ├── via │ │ ├── __init__.py │ │ └── views │ │ │ ├── __init__.py │ │ │ └── index_test.py │ ├── matchers.py │ ├── status_test.py │ └── conftest.py ├── data_directory │ ├── __init__.py │ ├── google_drive_resource_keys.json │ └── google_drive_credentials.json └── factories │ ├── __init__.py │ ├── video.py │ ├── transcript_info.py │ └── transcript.py ├── via ├── __init__.py ├── scripts │ └── __init__.py ├── views │ ├── api │ │ ├── __init__.py │ │ ├── youtube.py │ │ ├── transcript.py │ │ └── exceptions.py │ ├── proxy.py │ ├── status.py │ ├── webargs.py │ └── debug.py ├── static │ ├── favicon.ico │ ├── favicon.png │ ├── apple-touch-icon.png │ ├── apple-touch-icon-57x57.png │ ├── apple-touch-icon-precomposed.png │ ├── vendor │ │ └── pdfjs-2 │ │ │ └── web │ │ │ ├── cmaps │ │ │ ├── H.bcmap │ │ │ ├── V.bcmap │ │ │ ├── 78-H.bcmap │ │ │ ├── 78-V.bcmap │ │ │ ├── B5-H.bcmap │ │ │ ├── B5-V.bcmap │ │ │ ├── GB-H.bcmap │ │ │ ├── GB-V.bcmap │ │ │ ├── Add-H.bcmap │ │ │ ├── Add-V.bcmap │ │ │ ├── B5pc-H.bcmap │ │ │ ├── B5pc-V.bcmap │ │ │ ├── CNS1-H.bcmap │ │ │ ├── CNS1-V.bcmap │ │ │ ├── CNS2-H.bcmap │ │ │ ├── CNS2-V.bcmap │ │ │ ├── EUC-H.bcmap │ │ │ ├── EUC-V.bcmap │ │ │ ├── Ext-H.bcmap │ │ │ ├── Ext-V.bcmap │ │ │ ├── GBK2K-H.bcmap │ │ │ ├── GBK2K-V.bcmap │ │ │ ├── GBT-H.bcmap │ │ │ ├── GBT-V.bcmap │ │ │ ├── Hankaku.bcmap │ │ │ ├── KSC-H.bcmap │ │ │ ├── KSC-V.bcmap │ │ │ ├── NWP-H.bcmap │ │ │ ├── NWP-V.bcmap │ │ │ ├── RKSJ-H.bcmap │ │ │ ├── RKSJ-V.bcmap │ │ │ ├── Roman.bcmap │ │ │ ├── 78-EUC-H.bcmap │ │ │ ├── 78-EUC-V.bcmap │ │ │ ├── 78-RKSJ-H.bcmap │ │ │ ├── 78-RKSJ-V.bcmap │ │ │ ├── CNS-EUC-H.bcmap │ │ │ ├── CNS-EUC-V.bcmap │ │ │ ├── ETHK-B5-H.bcmap │ │ │ ├── ETHK-B5-V.bcmap │ │ │ ├── ETen-B5-H.bcmap │ │ │ ├── ETen-B5-V.bcmap │ │ │ ├── GB-EUC-H.bcmap │ │ │ ├── GB-EUC-V.bcmap │ │ │ ├── GBK-EUC-H.bcmap │ │ │ ├── GBK-EUC-V.bcmap │ │ │ ├── GBT-EUC-H.bcmap │ │ │ ├── GBT-EUC-V.bcmap │ │ │ ├── Hiragana.bcmap │ │ │ ├── KSC-EUC-H.bcmap │ │ │ ├── KSC-EUC-V.bcmap │ │ │ ├── Katakana.bcmap │ │ │ ├── WP-Symbol.bcmap │ │ │ ├── 78ms-RKSJ-H.bcmap │ │ │ ├── 78ms-RKSJ-V.bcmap │ │ │ ├── 83pv-RKSJ-H.bcmap │ │ │ ├── 90ms-RKSJ-H.bcmap │ │ │ ├── 90ms-RKSJ-V.bcmap │ │ │ ├── 90msp-RKSJ-H.bcmap │ │ │ ├── 90msp-RKSJ-V.bcmap │ │ │ ├── 90pv-RKSJ-H.bcmap │ │ │ ├── 90pv-RKSJ-V.bcmap │ │ │ ├── Add-RKSJ-H.bcmap │ │ │ ├── Add-RKSJ-V.bcmap │ │ │ ├── Adobe-CNS1-0.bcmap │ │ │ ├── Adobe-CNS1-1.bcmap │ │ │ ├── Adobe-CNS1-2.bcmap │ │ │ ├── Adobe-CNS1-3.bcmap │ │ │ ├── Adobe-CNS1-4.bcmap │ │ │ ├── Adobe-CNS1-5.bcmap │ │ │ ├── Adobe-CNS1-6.bcmap │ │ │ ├── Adobe-GB1-0.bcmap │ │ │ ├── Adobe-GB1-1.bcmap │ │ │ ├── Adobe-GB1-2.bcmap │ │ │ ├── Adobe-GB1-3.bcmap │ │ │ ├── Adobe-GB1-4.bcmap │ │ │ ├── Adobe-GB1-5.bcmap │ │ │ ├── ETenms-B5-H.bcmap │ │ │ ├── ETenms-B5-V.bcmap │ │ │ ├── Ext-RKSJ-H.bcmap │ │ │ ├── Ext-RKSJ-V.bcmap │ │ │ ├── GBKp-EUC-H.bcmap │ │ │ ├── GBKp-EUC-V.bcmap │ │ │ ├── GBTpc-EUC-H.bcmap │ │ │ ├── GBTpc-EUC-V.bcmap │ │ │ ├── GBpc-EUC-H.bcmap │ │ │ ├── GBpc-EUC-V.bcmap │ │ │ ├── HKdla-B5-H.bcmap │ │ │ ├── HKdla-B5-V.bcmap │ │ │ ├── HKdlb-B5-H.bcmap │ │ │ ├── HKdlb-B5-V.bcmap │ │ │ ├── HKgccs-B5-H.bcmap │ │ │ ├── HKgccs-B5-V.bcmap │ │ │ ├── HKm314-B5-H.bcmap │ │ │ ├── HKm314-B5-V.bcmap │ │ │ ├── HKm471-B5-H.bcmap │ │ │ ├── HKm471-B5-V.bcmap │ │ │ ├── HKscs-B5-H.bcmap │ │ │ ├── HKscs-B5-V.bcmap │ │ │ ├── KSC-Johab-H.bcmap │ │ │ ├── KSC-Johab-V.bcmap │ │ │ ├── KSCms-UHC-H.bcmap │ │ │ ├── KSCms-UHC-V.bcmap │ │ │ ├── KSCpc-EUC-H.bcmap │ │ │ ├── KSCpc-EUC-V.bcmap │ │ │ ├── UniGB-UCS2-H.bcmap │ │ │ ├── UniGB-UCS2-V.bcmap │ │ │ ├── UniGB-UTF8-H.bcmap │ │ │ ├── UniGB-UTF8-V.bcmap │ │ │ ├── UniKS-UCS2-H.bcmap │ │ │ ├── UniKS-UCS2-V.bcmap │ │ │ ├── UniKS-UTF8-H.bcmap │ │ │ ├── UniKS-UTF8-V.bcmap │ │ │ ├── Adobe-GB1-UCS2.bcmap │ │ │ ├── Adobe-Japan1-0.bcmap │ │ │ ├── Adobe-Japan1-1.bcmap │ │ │ ├── Adobe-Japan1-2.bcmap │ │ │ ├── Adobe-Japan1-3.bcmap │ │ │ ├── Adobe-Japan1-4.bcmap │ │ │ ├── Adobe-Japan1-5.bcmap │ │ │ ├── Adobe-Japan1-6.bcmap │ │ │ ├── Adobe-Korea1-0.bcmap │ │ │ ├── Adobe-Korea1-1.bcmap │ │ │ ├── Adobe-Korea1-2.bcmap │ │ │ ├── KSCms-UHC-HW-H.bcmap │ │ │ ├── KSCms-UHC-HW-V.bcmap │ │ │ ├── UniCNS-UCS2-H.bcmap │ │ │ ├── UniCNS-UCS2-V.bcmap │ │ │ ├── UniCNS-UTF16-H.bcmap │ │ │ ├── UniCNS-UTF16-V.bcmap │ │ │ ├── UniCNS-UTF32-H.bcmap │ │ │ ├── UniCNS-UTF32-V.bcmap │ │ │ ├── UniCNS-UTF8-H.bcmap │ │ │ ├── UniCNS-UTF8-V.bcmap │ │ │ ├── UniGB-UTF16-H.bcmap │ │ │ ├── UniGB-UTF16-V.bcmap │ │ │ ├── UniGB-UTF32-H.bcmap │ │ │ ├── UniGB-UTF32-V.bcmap │ │ │ ├── UniJIS-UCS2-H.bcmap │ │ │ ├── UniJIS-UCS2-V.bcmap │ │ │ ├── UniJIS-UTF16-H.bcmap │ │ │ ├── UniJIS-UTF16-V.bcmap │ │ │ ├── UniJIS-UTF32-H.bcmap │ │ │ ├── UniJIS-UTF32-V.bcmap │ │ │ ├── UniJIS-UTF8-H.bcmap │ │ │ ├── UniJIS-UTF8-V.bcmap │ │ │ ├── UniKS-UTF16-H.bcmap │ │ │ ├── UniKS-UTF16-V.bcmap │ │ │ ├── UniKS-UTF32-H.bcmap │ │ │ ├── UniKS-UTF32-V.bcmap │ │ │ ├── Adobe-CNS1-UCS2.bcmap │ │ │ ├── Adobe-Japan1-UCS2.bcmap │ │ │ ├── Adobe-Korea1-UCS2.bcmap │ │ │ ├── UniJIS-UCS2-HW-H.bcmap │ │ │ ├── UniJIS-UCS2-HW-V.bcmap │ │ │ ├── UniJIS2004-UTF8-H.bcmap │ │ │ ├── UniJIS2004-UTF8-V.bcmap │ │ │ ├── UniJISPro-UCS2-V.bcmap │ │ │ ├── UniJISPro-UTF8-V.bcmap │ │ │ ├── UniJIS2004-UTF16-H.bcmap │ │ │ ├── UniJIS2004-UTF16-V.bcmap │ │ │ ├── UniJIS2004-UTF32-H.bcmap │ │ │ ├── UniJIS2004-UTF32-V.bcmap │ │ │ ├── UniJISPro-UCS2-HW-V.bcmap │ │ │ ├── UniJISX0213-UTF32-H.bcmap │ │ │ ├── UniJISX0213-UTF32-V.bcmap │ │ │ ├── UniJISX02132004-UTF32-H.bcmap │ │ │ └── UniJISX02132004-UTF32-V.bcmap │ │ │ ├── images │ │ │ ├── shadow.png │ │ │ ├── treeitem-collapsed.svg │ │ │ ├── treeitem-expanded.svg │ │ │ ├── loading-icon.gif │ │ │ ├── toolbarButton-bookmark.svg │ │ │ ├── secondaryToolbarButton-spreadNone.svg │ │ │ ├── annotation-noicon.svg │ │ │ ├── secondaryToolbarButton-scrollPage.svg │ │ │ ├── secondaryToolbarButton-rotateCcw.svg │ │ │ ├── toolbarButton-menuArrow.svg │ │ │ ├── secondaryToolbarButton-firstPage.svg │ │ │ ├── secondaryToolbarButton-lastPage.svg │ │ │ ├── secondaryToolbarButton-scrollHorizontal.svg │ │ │ ├── secondaryToolbarButton-scrollVertical.svg │ │ │ ├── secondaryToolbarButton-handTool.svg │ │ │ ├── toolbarButton-viewAttachments.svg │ │ │ ├── toolbarButton-zoomOut.svg │ │ │ ├── toolbarButton-presentationMode.svg │ │ │ ├── toolbarButton-zoomIn.svg │ │ │ ├── findbarButton-next.svg │ │ │ ├── findbarButton-previous.svg │ │ │ ├── annotation-insert.svg │ │ │ ├── toolbarButton-search.svg │ │ │ ├── annotation-check.svg │ │ │ ├── toolbarButton-viewOutline.svg │ │ │ ├── annotation-newparagraph.svg │ │ │ ├── secondaryToolbarButton-scrollWrapped.svg │ │ │ ├── secondaryToolbarButton-selectTool.svg │ │ │ ├── secondaryToolbarButton-rotateCw.svg │ │ │ ├── toolbarButton-pageDown.svg │ │ │ ├── toolbarButton-pageUp.svg │ │ │ ├── toolbarButton-secondaryToolbarToggle.svg │ │ │ ├── toolbarButton-currentOutlineItem.svg │ │ │ ├── toolbarButton-print.svg │ │ │ ├── secondaryToolbarButton-documentProperties.svg │ │ │ ├── toolbarButton-download.svg │ │ │ ├── toolbarButton-viewThumbnail.svg │ │ │ ├── toolbarButton-sidebarToggle.svg │ │ │ ├── toolbarButton-openFile.svg │ │ │ ├── secondaryToolbarButton-spreadOdd.svg │ │ │ ├── toolbarButton-viewLayers.svg │ │ │ ├── annotation-comment.svg │ │ │ ├── secondaryToolbarButton-spreadEven.svg │ │ │ ├── annotation-paragraph.svg │ │ │ ├── annotation-note.svg │ │ │ ├── annotation-key.svg │ │ │ ├── loading.svg │ │ │ └── loading-dark.svg │ │ │ └── standard_fonts │ │ │ ├── FoxitFixed.pfb │ │ │ ├── FoxitSans.pfb │ │ │ ├── FoxitSerif.pfb │ │ │ ├── FoxitSymbol.pfb │ │ │ ├── FoxitDingbats.pfb │ │ │ ├── FoxitFixedBold.pfb │ │ │ ├── FoxitSansBold.pfb │ │ │ ├── FoxitSansItalic.pfb │ │ │ ├── FoxitSerifBold.pfb │ │ │ ├── FoxitFixedItalic.pfb │ │ │ ├── FoxitSerifItalic.pfb │ │ │ ├── FoxitFixedBoldItalic.pfb │ │ │ ├── FoxitSansBoldItalic.pfb │ │ │ ├── FoxitSerifBoldItalic.pfb │ │ │ ├── LiberationSans-Bold.ttf │ │ │ ├── LiberationSans-Italic.ttf │ │ │ ├── LiberationSans-Regular.ttf │ │ │ ├── LiberationSans-BoldItalic.ttf │ │ │ └── LICENSE_FOXIT │ ├── apple-touch-icon-57x57-precomposed.png │ ├── scripts │ │ ├── video_player │ │ │ ├── components │ │ │ │ ├── icons │ │ │ │ │ ├── README.md │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── Up.tsx │ │ │ │ │ ├── Down.tsx │ │ │ │ │ ├── Play.tsx │ │ │ │ │ ├── Pause.tsx │ │ │ │ │ └── Sync.tsx │ │ │ │ ├── TranscriptError.tsx │ │ │ │ ├── test │ │ │ │ │ ├── HypothesisClient-test.js │ │ │ │ │ └── TranscriptError-test.js │ │ │ │ ├── HypothesisClient.tsx │ │ │ │ └── CopyButton.tsx │ │ │ ├── utils │ │ │ │ ├── youtube.ts │ │ │ │ ├── test │ │ │ │ │ ├── youtube-test.js │ │ │ │ │ └── next-render-test.js │ │ │ │ └── next-render.ts │ │ │ ├── config.ts │ │ │ └── test │ │ │ │ └── config-test.js │ │ ├── types │ │ │ └── css-custom-highlights.d.ts │ │ ├── setup-tests.js │ │ ├── tsconfig.json │ │ └── test-util │ │ │ └── video-player-fixtures.ts │ ├── images │ │ └── icons │ │ │ ├── play.svg │ │ │ ├── up.svg │ │ │ ├── down.svg │ │ │ ├── pause.svg │ │ │ └── sync.svg │ ├── js │ │ └── pdfjs-init.min.js │ ├── css │ │ └── img │ │ │ └── icons.svg │ └── styles │ │ └── video_player.css ├── requests_tools │ └── __init__.py ├── assets.ini ├── models │ ├── __init__.py │ ├── video.py │ ├── transcript.py │ └── _mixins.py ├── migrations │ ├── script.py.mako │ └── versions │ │ ├── d4e3e1bf95eb_add_the_video_table.py │ │ └── 9a37efe13a91_add_the_transcript_table.py ├── sentry_filters.py ├── tweens.py ├── pshell.py ├── assets.py ├── services │ ├── via_client.py │ └── transcript.py ├── security.py ├── templates │ └── view_video.html.jinja2 └── db.py ├── .python-version ├── .cookiecutter ├── includes │ ├── coverage │ │ └── omit │ ├── tox │ │ ├── allowlist_externals │ │ ├── passenv │ │ ├── commands │ │ └── setenv │ ├── tests │ │ └── pylint │ │ │ └── disables │ ├── hacking │ │ └── setting_up.md │ ├── development.ini │ ├── conf │ │ ├── supervisord.conf.json │ │ └── supervisord-dev.conf.json │ ├── gitignore │ ├── requirements │ │ └── prod.in │ ├── .github │ │ ├── dependabot │ │ │ └── npm │ │ │ │ └── tail.yml │ │ └── workflows │ │ │ └── environments.json │ └── docker-compose │ │ └── services.yml └── cookiecutter.json ├── .prettierrc ├── requirements ├── format.in ├── build.in ├── checkformatting.in ├── coverage.in ├── template.in ├── typecheck.in ├── lint.in ├── updatepdfjs.in ├── dev.in ├── functests.in ├── tests.in ├── prod.in ├── build.txt ├── updatepdfjs.txt ├── format.txt ├── coverage.txt └── checkformatting.txt ├── .yarnrc.yml ├── conf ├── gunicorn.conf.py ├── minify_assets.json ├── nginx │ ├── includes.dev │ │ ├── resolver.conf │ │ └── app_upstream.conf │ ├── includes │ │ ├── robots.conf │ │ ├── abuse_response_headers.conf │ │ ├── resolver.conf │ │ ├── app_upstream.conf │ │ └── errors.conf │ ├── envsubst.conf.template │ └── dev_host_bridge.sh ├── gunicorn-dev.conf.py ├── alembic.ini ├── supervisord.conf ├── development.ini ├── production.ini └── supervisord-dev.conf ├── via.mk ├── .prettierignore ├── bin ├── make_docker_run ├── create-db ├── make_python ├── make_devdata ├── make_template └── install-python ├── .docker.env ├── .gitignore ├── .github ├── workflows │ ├── slack.yml │ ├── frontend.yml │ └── keepalive.yml └── dependabot.yml ├── .babelrc ├── frontend.mk ├── vitest.config.js ├── LICENSE ├── docker-compose.yml ├── gulpfile.js └── eslint.config.js /.devdata/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /via/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /via/scripts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.11.9 2 | -------------------------------------------------------------------------------- /tests/common/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/functional/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/via/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /via/views/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/data_directory/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/functional/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/functional/via/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/via/views/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/functional/api/views/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/functional/via/views/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/via/services/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/via/views/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/via/requests_tools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/via/services/fixtures/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.cookiecutter/includes/coverage/omit: -------------------------------------------------------------------------------- 1 | "via/pshell.py", 2 | -------------------------------------------------------------------------------- /tests/data_directory/google_drive_resource_keys.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.cookiecutter/includes/tox/allowlist_externals: -------------------------------------------------------------------------------- 1 | updatepdfjs: sh 2 | -------------------------------------------------------------------------------- /tests/data_directory/google_drive_credentials.json: -------------------------------------------------------------------------------- 1 | [{"disable": true}] 2 | -------------------------------------------------------------------------------- /.cookiecutter/includes/tests/pylint/disables: -------------------------------------------------------------------------------- 1 | "too-many-positional-arguments", 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /requirements/format.in: -------------------------------------------------------------------------------- 1 | pip==25.3 2 | pip-tools 3 | pip-sync-faster 4 | ruff 5 | -------------------------------------------------------------------------------- /requirements/build.in: -------------------------------------------------------------------------------- 1 | pip==25.3 2 | pip-tools 3 | pip-sync-faster 4 | whitenoise 5 | -------------------------------------------------------------------------------- /requirements/checkformatting.in: -------------------------------------------------------------------------------- 1 | pip==25.3 2 | pip-tools 3 | pip-sync-faster 4 | ruff 5 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-3.6.1.cjs 4 | -------------------------------------------------------------------------------- /requirements/coverage.in: -------------------------------------------------------------------------------- 1 | pip==25.3 2 | pip-tools 3 | pip-sync-faster 4 | coverage[toml] 5 | -------------------------------------------------------------------------------- /requirements/template.in: -------------------------------------------------------------------------------- 1 | pip==25.3 2 | pip-tools 3 | pip-sync-faster 4 | cookiecutter 5 | -------------------------------------------------------------------------------- /conf/gunicorn.conf.py: -------------------------------------------------------------------------------- 1 | bind = "unix:/tmp/gunicorn-web.sock" 2 | worker_tmp_dir = "/dev/shm" 3 | -------------------------------------------------------------------------------- /requirements/typecheck.in: -------------------------------------------------------------------------------- 1 | -r prod.txt 2 | 3 | pip-tools 4 | pip-sync-faster 5 | 6 | mypy 7 | -------------------------------------------------------------------------------- /via/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/favicon.ico -------------------------------------------------------------------------------- /via/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/favicon.png -------------------------------------------------------------------------------- /requirements/lint.in: -------------------------------------------------------------------------------- 1 | pip-tools 2 | pip-sync-faster 3 | -r tests.txt 4 | -r functests.txt 5 | ruff 6 | -------------------------------------------------------------------------------- /requirements/updatepdfjs.in: -------------------------------------------------------------------------------- 1 | pip==25.3 2 | pip-tools 3 | pip-sync-faster 4 | importlib_resources 5 | -------------------------------------------------------------------------------- /via/requests_tools/__init__.py: -------------------------------------------------------------------------------- 1 | from via.requests_tools.headers import add_request_headers, clean_headers 2 | -------------------------------------------------------------------------------- /via/static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/apple-touch-icon.png -------------------------------------------------------------------------------- /.cookiecutter/includes/hacking/setting_up.md: -------------------------------------------------------------------------------- 1 | To run Via locally run `make dev` and visit http://localhost:9083. 2 | -------------------------------------------------------------------------------- /requirements/dev.in: -------------------------------------------------------------------------------- 1 | pip-tools 2 | pip-sync-faster 3 | -r prod.txt 4 | factory-boy 5 | pyramid-ipython 6 | supervisor 7 | -------------------------------------------------------------------------------- /via.mk: -------------------------------------------------------------------------------- 1 | .PHONY: nginx 2 | nginx: python 3 | @tox -qe dev --run-command 'docker compose run --rm --service-ports nginx-proxy' 4 | -------------------------------------------------------------------------------- /via/static/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /via/static/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/V.bcmap -------------------------------------------------------------------------------- /conf/minify_assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "via/static/js/*.js": {}, 3 | "via/static/vendor/**/*": { 4 | "in_place": true 5 | } 6 | } -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/78-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/78-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/78-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/78-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/B5-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/B5-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/B5-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/B5-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/GB-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/GB-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/GB-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/GB-V.bcmap -------------------------------------------------------------------------------- /via/static/apple-touch-icon-57x57-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/apple-touch-icon-57x57-precomposed.png -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Add-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Add-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Add-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Add-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/B5pc-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/B5pc-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/B5pc-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/B5pc-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/CNS1-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/CNS1-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/CNS1-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/CNS1-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/CNS2-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/CNS2-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/CNS2-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/CNS2-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/EUC-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/EUC-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/EUC-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/EUC-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Ext-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Ext-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Ext-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Ext-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/GBK2K-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/GBK2K-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/GBK2K-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/GBK2K-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/GBT-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/GBT-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/GBT-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/GBT-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Hankaku.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Hankaku.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/KSC-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/KSC-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/KSC-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/KSC-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/NWP-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/NWP-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/NWP-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/NWP-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/RKSJ-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/RKSJ-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/RKSJ-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/RKSJ-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Roman.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Roman.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/images/shadow.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore minified JS assets - these should be moved to `build/scripts/` in future. 2 | via/static/js/ 3 | 4 | via/static/vendor/ 5 | -------------------------------------------------------------------------------- /via/assets.ini: -------------------------------------------------------------------------------- 1 | [bundles] 2 | 3 | video_player_css = 4 | styles/video_player.css 5 | 6 | video_player_js = 7 | scripts/video_player.bundle.js 8 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/78-EUC-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/78-EUC-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/78-EUC-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/78-EUC-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/78-RKSJ-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/78-RKSJ-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/78-RKSJ-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/78-RKSJ-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/CNS-EUC-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/CNS-EUC-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/CNS-EUC-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/CNS-EUC-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/ETHK-B5-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/ETHK-B5-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/ETHK-B5-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/ETHK-B5-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/ETen-B5-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/ETen-B5-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/ETen-B5-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/ETen-B5-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/GB-EUC-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/GB-EUC-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/GB-EUC-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/GB-EUC-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/GBK-EUC-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/GBK-EUC-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/GBK-EUC-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/GBK-EUC-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/GBT-EUC-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/GBT-EUC-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/GBT-EUC-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/GBT-EUC-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Hiragana.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Hiragana.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/KSC-EUC-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/KSC-EUC-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/KSC-EUC-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/KSC-EUC-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Katakana.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Katakana.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/WP-Symbol.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/WP-Symbol.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/treeitem-collapsed.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/treeitem-expanded.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/78ms-RKSJ-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/78ms-RKSJ-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/78ms-RKSJ-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/78ms-RKSJ-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/83pv-RKSJ-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/83pv-RKSJ-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/90ms-RKSJ-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/90ms-RKSJ-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/90ms-RKSJ-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/90ms-RKSJ-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/90msp-RKSJ-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/90msp-RKSJ-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/90msp-RKSJ-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/90msp-RKSJ-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/90pv-RKSJ-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/90pv-RKSJ-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/90pv-RKSJ-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/90pv-RKSJ-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Add-RKSJ-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Add-RKSJ-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Add-RKSJ-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Add-RKSJ-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-CNS1-0.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-CNS1-0.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-CNS1-1.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-CNS1-1.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-CNS1-2.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-CNS1-2.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-CNS1-3.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-CNS1-3.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-CNS1-4.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-CNS1-4.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-CNS1-5.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-CNS1-5.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-CNS1-6.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-CNS1-6.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-GB1-0.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-GB1-0.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-GB1-1.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-GB1-1.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-GB1-2.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-GB1-2.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-GB1-3.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-GB1-3.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-GB1-4.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-GB1-4.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-GB1-5.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-GB1-5.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/ETenms-B5-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/ETenms-B5-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/ETenms-B5-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/ETenms-B5-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Ext-RKSJ-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Ext-RKSJ-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Ext-RKSJ-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Ext-RKSJ-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/GBKp-EUC-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/GBKp-EUC-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/GBKp-EUC-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/GBKp-EUC-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/GBTpc-EUC-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/GBTpc-EUC-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/GBTpc-EUC-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/GBTpc-EUC-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/GBpc-EUC-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/GBpc-EUC-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/GBpc-EUC-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/GBpc-EUC-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/HKdla-B5-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/HKdla-B5-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/HKdla-B5-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/HKdla-B5-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/HKdlb-B5-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/HKdlb-B5-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/HKdlb-B5-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/HKdlb-B5-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/HKgccs-B5-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/HKgccs-B5-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/HKgccs-B5-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/HKgccs-B5-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/HKm314-B5-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/HKm314-B5-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/HKm314-B5-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/HKm314-B5-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/HKm471-B5-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/HKm471-B5-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/HKm471-B5-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/HKm471-B5-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/HKscs-B5-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/HKscs-B5-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/HKscs-B5-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/HKscs-B5-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/KSC-Johab-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/KSC-Johab-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/KSC-Johab-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/KSC-Johab-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/KSCms-UHC-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/KSCms-UHC-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/KSCms-UHC-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/KSCms-UHC-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/KSCpc-EUC-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/KSCpc-EUC-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/KSCpc-EUC-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/KSCpc-EUC-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniGB-UCS2-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniGB-UCS2-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniGB-UCS2-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniGB-UCS2-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniGB-UTF8-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniGB-UTF8-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniGB-UTF8-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniGB-UTF8-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniKS-UCS2-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniKS-UCS2-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniKS-UCS2-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniKS-UCS2-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniKS-UTF8-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniKS-UTF8-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniKS-UTF8-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniKS-UTF8-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/loading-icon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/images/loading-icon.gif -------------------------------------------------------------------------------- /tests/unit/via/views/conftest.py: -------------------------------------------------------------------------------- 1 | # Import this so that our webargs customization is registered in the view tests. 2 | import via.views.webargs # noqa: F401 3 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-GB1-UCS2.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-GB1-UCS2.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-Japan1-0.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-Japan1-0.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-Japan1-1.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-Japan1-1.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-Japan1-2.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-Japan1-2.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-Japan1-3.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-Japan1-3.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-Japan1-4.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-Japan1-4.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-Japan1-5.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-Japan1-5.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-Japan1-6.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-Japan1-6.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-Korea1-0.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-Korea1-0.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-Korea1-1.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-Korea1-1.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-Korea1-2.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-Korea1-2.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/KSCms-UHC-HW-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/KSCms-UHC-HW-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/KSCms-UHC-HW-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/KSCms-UHC-HW-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniCNS-UCS2-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniCNS-UCS2-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniCNS-UCS2-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniCNS-UCS2-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniCNS-UTF16-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniCNS-UTF16-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniCNS-UTF16-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniCNS-UTF16-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniCNS-UTF32-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniCNS-UTF32-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniCNS-UTF32-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniCNS-UTF32-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniCNS-UTF8-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniCNS-UTF8-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniCNS-UTF8-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniCNS-UTF8-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniGB-UTF16-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniGB-UTF16-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniGB-UTF16-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniGB-UTF16-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniGB-UTF32-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniGB-UTF32-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniGB-UTF32-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniGB-UTF32-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJIS-UCS2-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJIS-UCS2-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJIS-UCS2-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJIS-UCS2-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJIS-UTF16-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJIS-UTF16-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJIS-UTF16-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJIS-UTF16-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJIS-UTF32-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJIS-UTF32-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJIS-UTF32-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJIS-UTF32-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJIS-UTF8-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJIS-UTF8-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJIS-UTF8-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJIS-UTF8-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniKS-UTF16-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniKS-UTF16-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniKS-UTF16-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniKS-UTF16-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniKS-UTF32-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniKS-UTF32-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniKS-UTF32-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniKS-UTF32-V.bcmap -------------------------------------------------------------------------------- /.cookiecutter/includes/tox/passenv: -------------------------------------------------------------------------------- 1 | dev: CHROME_EXTENSION_ID 2 | dev: NGINX_SERVER 3 | dev: CLIENT_EMBED_URL 4 | dev: SIGNED_URLS_REQUIRED 5 | dev: YOUTUBE_API_KEY 6 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-CNS1-UCS2.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-CNS1-UCS2.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-Japan1-UCS2.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-Japan1-UCS2.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/Adobe-Korea1-UCS2.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/Adobe-Korea1-UCS2.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJIS-UCS2-HW-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJIS-UCS2-HW-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJIS-UCS2-HW-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJIS-UCS2-HW-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJIS2004-UTF8-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJIS2004-UTF8-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJIS2004-UTF8-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJIS2004-UTF8-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJISPro-UCS2-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJISPro-UCS2-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJISPro-UTF8-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJISPro-UTF8-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/standard_fonts/FoxitFixed.pfb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/standard_fonts/FoxitFixed.pfb -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/standard_fonts/FoxitSans.pfb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/standard_fonts/FoxitSans.pfb -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/standard_fonts/FoxitSerif.pfb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/standard_fonts/FoxitSerif.pfb -------------------------------------------------------------------------------- /conf/nginx/includes.dev/resolver.conf: -------------------------------------------------------------------------------- 1 | # Use Cloudflare DNS (https://1.1.1.1/dns/) to resolve locally as the AWS 2 | # resolver is not available 3 | resolver 1.1.1.1 ipv6=off; -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJIS2004-UTF16-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJIS2004-UTF16-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJIS2004-UTF16-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJIS2004-UTF16-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJIS2004-UTF32-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJIS2004-UTF32-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJIS2004-UTF32-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJIS2004-UTF32-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJISPro-UCS2-HW-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJISPro-UCS2-HW-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJISX0213-UTF32-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJISX0213-UTF32-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJISX0213-UTF32-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJISX0213-UTF32-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/standard_fonts/FoxitSymbol.pfb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/standard_fonts/FoxitSymbol.pfb -------------------------------------------------------------------------------- /.cookiecutter/includes/development.ini: -------------------------------------------------------------------------------- 1 | nginx_server: http://localhost:9083 2 | client_embed_url: http://localhost:5000/embed.js 3 | via_html_url: http://localhost:9085/proxy 4 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/toolbarButton-bookmark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/standard_fonts/FoxitDingbats.pfb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/standard_fonts/FoxitDingbats.pfb -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/standard_fonts/FoxitFixedBold.pfb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/standard_fonts/FoxitFixedBold.pfb -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/standard_fonts/FoxitSansBold.pfb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/standard_fonts/FoxitSansBold.pfb -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/standard_fonts/FoxitSansItalic.pfb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/standard_fonts/FoxitSansItalic.pfb -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/standard_fonts/FoxitSerifBold.pfb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/standard_fonts/FoxitSerifBold.pfb -------------------------------------------------------------------------------- /via/models/__init__.py: -------------------------------------------------------------------------------- 1 | from via.models.transcript import Transcript 2 | from via.models.video import Video 3 | 4 | 5 | def includeme(_config): # pragma: no cover 6 | pass 7 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJISX02132004-UTF32-H.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJISX02132004-UTF32-H.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/cmaps/UniJISX02132004-UTF32-V.bcmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/cmaps/UniJISX02132004-UTF32-V.bcmap -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/standard_fonts/FoxitFixedItalic.pfb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/standard_fonts/FoxitFixedItalic.pfb -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/standard_fonts/FoxitSerifItalic.pfb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/standard_fonts/FoxitSerifItalic.pfb -------------------------------------------------------------------------------- /conf/gunicorn-dev.conf.py: -------------------------------------------------------------------------------- 1 | from glob import glob 2 | 3 | bind = "0.0.0.0:9082" 4 | reload = True 5 | reload_extra_files = glob("via/templates/**/*", recursive=True) 6 | timeout = 0 7 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/standard_fonts/FoxitFixedBoldItalic.pfb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/standard_fonts/FoxitFixedBoldItalic.pfb -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/standard_fonts/FoxitSansBoldItalic.pfb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/standard_fonts/FoxitSansBoldItalic.pfb -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/standard_fonts/FoxitSerifBoldItalic.pfb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/standard_fonts/FoxitSerifBoldItalic.pfb -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/standard_fonts/LiberationSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/standard_fonts/LiberationSans-Bold.ttf -------------------------------------------------------------------------------- /requirements/functests.in: -------------------------------------------------------------------------------- 1 | pip-tools 2 | pip-sync-faster 3 | -r prod.txt 4 | httpretty 5 | pytest 6 | factory-boy 7 | pytest-factoryboy 8 | h-matchers 9 | h-testkit 10 | webtest 11 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/standard_fonts/LiberationSans-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/standard_fonts/LiberationSans-Italic.ttf -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/standard_fonts/LiberationSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/standard_fonts/LiberationSans-Regular.ttf -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/standard_fonts/LiberationSans-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypothesis/via/HEAD/via/static/vendor/pdfjs-2/web/standard_fonts/LiberationSans-BoldItalic.ttf -------------------------------------------------------------------------------- /.cookiecutter/includes/tox/commands: -------------------------------------------------------------------------------- 1 | updatepdfjs: sh bin/update-pdfjs 2 | build: python bin/minify_assets.py -c conf/minify_assets.json 3 | build: python -m whitenoise.compress --no-brotli via/static 4 | -------------------------------------------------------------------------------- /requirements/tests.in: -------------------------------------------------------------------------------- 1 | pip-tools 2 | pip-sync-faster 3 | -r prod.txt 4 | pytest 5 | pytest-cov 6 | factory-boy 7 | pytest-factoryboy 8 | h-matchers 9 | h-testkit 10 | httpretty 11 | freezegun 12 | -------------------------------------------------------------------------------- /tests/factories/__init__.py: -------------------------------------------------------------------------------- 1 | from tests.factories.transcript import TranscriptFactory 2 | from tests.factories.transcript_info import TranscriptInfoFactory 3 | from tests.factories.video import VideoFactory 4 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/secondaryToolbarButton-spreadNone.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /conf/nginx/includes/robots.conf: -------------------------------------------------------------------------------- 1 | # Tell Google not to index the page and not to follow links on the page. 2 | # 3 | # https://developers.google.com/search/reference/robots_meta_tag 4 | 5 | add_header "X-Robots-Tag" "noindex, nofollow" always; 6 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/annotation-noicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/secondaryToolbarButton-scrollPage.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bin/make_docker_run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker run --rm \ 3 | -v $(pwd)/.devdata/:/via-data/:ro \ 4 | --add-host host.docker.internal:host-gateway \ 5 | --env-file .docker.env \ 6 | --env-file .devdata.env \ 7 | -p 9083:9083 \ 8 | hypothesis/via:dev 9 | -------------------------------------------------------------------------------- /conf/nginx/includes/abuse_response_headers.conf: -------------------------------------------------------------------------------- 1 | # Add links to our abuse policy in response headers. 2 | 3 | add_header "X-Abuse-Policy" "https://web.hypothes.is/abuse-policy/" always; 4 | add_header "X-Complaints-To" "https://web.hypothes.is/report-abuse/" always; 5 | -------------------------------------------------------------------------------- /tests/functional/api/view_status_test.py: -------------------------------------------------------------------------------- 1 | class TestViewMonitoring: 2 | def test_status_view(self, test_app): 3 | response = test_app.get("/_status") 4 | 5 | assert response.json == {"status": "okay"} 6 | assert response.status_code == 200 7 | -------------------------------------------------------------------------------- /tests/unit/via/assets_test.py: -------------------------------------------------------------------------------- 1 | from via.assets import includeme 2 | 3 | 4 | def test_includeme(pyramid_config): 5 | includeme(pyramid_config) 6 | 7 | assets_env = pyramid_config.registry["assets_env"] 8 | assert assets_env.assets_base_url == "/assets" 9 | -------------------------------------------------------------------------------- /bin/create-db: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # 3 | # Create the given database in Postgres, if it doesn't exist already. 4 | # 5 | # Usage: 6 | # 7 | # create-db 8 | make services args="exec postgres psql -U postgres -c 'CREATE DATABASE $1;'" > /dev/null 2>&1 || true 9 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/secondaryToolbarButton-rotateCcw.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/toolbarButton-menuArrow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /via/static/scripts/video_player/components/icons/README.md: -------------------------------------------------------------------------------- 1 | Icon components in this directory are auto-generated by the `generate-icons` script in the [@hypothesis/frontend-shared](https://github.com/hypothesis/frontend-shared) package. SVG source files are in `via/static/images/icons`. 2 | -------------------------------------------------------------------------------- /conf/nginx/envsubst.conf.template: -------------------------------------------------------------------------------- 1 | # Set nginx variables from environment variables. 2 | # The envsubst command that we run during our Docker build replaces the 3 | # ${FOO}'s in this file with values from environment variables. 4 | 5 | set $nginx_secure_link_secret "${NGINX_SECURE_LINK_SECRET}"; 6 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/secondaryToolbarButton-firstPage.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/secondaryToolbarButton-lastPage.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.cookiecutter/includes/conf/supervisord.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "programs": { 3 | "nginx": { 4 | "command": "nginx" 5 | }, 6 | "web": { 7 | "command": "newrelic-admin run-program gunicorn --paste conf/production.ini --config conf/gunicorn.conf.py" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.docker.env: -------------------------------------------------------------------------------- 1 | CHECKMATE_API_KEY=dummy 2 | CHECKMATE_URL=http://dummy-checkmate-service 3 | CLIENT_EMBED_URL=http://localhost:5000/embed.js 4 | DATA_DIRECTORY=/via-data/ 5 | NGINX_SECURE_LINK_SECRET=dummy 6 | NGINX_SERVER=http://localhost:9083 7 | VIA_HTML_URL=http://localhost:9085 8 | VIA_SECRET=dummy 9 | -------------------------------------------------------------------------------- /conf/nginx/includes.dev/app_upstream.conf: -------------------------------------------------------------------------------- 1 | # Define the "web" server (the Via Gunicorn/Pyramid app) for proxying to with: 2 | # 3 | # proxy_pass http://web; 4 | # 5 | # http://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream 6 | 7 | upstream web { 8 | server host.docker.internal:9082; 9 | } 10 | -------------------------------------------------------------------------------- /.cookiecutter/includes/gitignore: -------------------------------------------------------------------------------- 1 | via/static/**/*.gz 2 | build.tar 3 | 4 | # See https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored. 5 | # This is the "If you're not using Zero-Installs" list. 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/sdks 12 | !.yarn/versions 13 | -------------------------------------------------------------------------------- /.cookiecutter/includes/requirements/prod.in: -------------------------------------------------------------------------------- 1 | checkmatelib 2 | h-assets 3 | sentry-sdk 4 | h-pyramid-sentry 5 | h-vialib 6 | importlib_resources 7 | pyjwt 8 | pyramid-exclog 9 | pyramid-jinja2 10 | pyramid-sanity 11 | pyramid-services 12 | requests 13 | whitenoise 14 | google-auth-oauthlib 15 | marshmallow 16 | webargs 17 | webvtt-py 18 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/secondaryToolbarButton-scrollHorizontal.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/secondaryToolbarButton-scrollVertical.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /via/static/scripts/video_player/components/icons/index.ts: -------------------------------------------------------------------------------- 1 | // This file was auto-generated using scripts/generate-icons.js 2 | export { default as DownIcon } from './Down'; 3 | export { default as PauseIcon } from './Pause'; 4 | export { default as PlayIcon } from './Play'; 5 | export { default as SyncIcon } from './Sync'; 6 | export { default as UpIcon } from './Up'; 7 | -------------------------------------------------------------------------------- /tests/factories/video.py: -------------------------------------------------------------------------------- 1 | from factory import Sequence 2 | from factory.alchemy import SQLAlchemyModelFactory 3 | 4 | from via.models import Video 5 | 6 | 7 | class VideoFactory(SQLAlchemyModelFactory): 8 | class Meta: 9 | model = Video 10 | 11 | video_id = Sequence(lambda n: f"video_id_{n}") 12 | title = Sequence(lambda n: f"video_title_{n}") 13 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/secondaryToolbarButton-handTool.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/toolbarButton-viewAttachments.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/matchers.py: -------------------------------------------------------------------------------- 1 | """Objects that compare equal to other objects for testing.""" 2 | 3 | from h_matchers import Any 4 | from pyramid.response import Response 5 | 6 | 7 | def temporary_redirect_to(location): 8 | """Return a matcher for any `HTTP 302 Found` redirect to the given URL.""" 9 | return Any.instance_of(Response).with_attrs( 10 | {"status_code": 302, "location": location} 11 | ) 12 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/toolbarButton-zoomOut.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /tests/functional/matchers.py: -------------------------------------------------------------------------------- 1 | """Objects that compare equal to other objects for testing.""" 2 | 3 | from h_matchers import Any 4 | from webtest import TestResponse 5 | 6 | 7 | def temporary_redirect_to(location): 8 | """Return a matcher for any `HTTP 302 Found` redirect to the given URL.""" 9 | return Any.instance_of(TestResponse).with_attrs( 10 | {"status_code": 302, "location": location} 11 | ) 12 | -------------------------------------------------------------------------------- /via/models/video.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.orm import Mapped, mapped_column 2 | 3 | from via.db import Base 4 | from via.models._mixins import AutoincrementingIntegerIDMixin, CreatedUpdatedMixin 5 | 6 | 7 | class Video(AutoincrementingIntegerIDMixin, CreatedUpdatedMixin, Base): 8 | __tablename__ = "video" 9 | 10 | video_id: Mapped[str] = mapped_column(unique=True) 11 | title: Mapped[str] = mapped_column(repr=False) 12 | -------------------------------------------------------------------------------- /tests/unit/via/services/fixtures/google_403_not_shared.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "errors": [ 4 | { 5 | "domain": "global", 6 | "reason": "cannotDownloadFile", 7 | "message": "This file cannot be downloaded by the user." 8 | } 9 | ], 10 | "code": 403, 11 | "message": "This file cannot be downloaded by the user." 12 | } 13 | } -------------------------------------------------------------------------------- /tests/factories/transcript_info.py: -------------------------------------------------------------------------------- 1 | from factory import Factory, Sequence 2 | 3 | from via.services.youtube_transcript import TranscriptInfo 4 | 5 | 6 | class TranscriptInfoFactory(Factory): 7 | class Meta: 8 | model = TranscriptInfo 9 | 10 | language_code = "en-us" 11 | name = "English (United States)" 12 | url = Sequence(lambda n: f"https://example.com/api/timedtext?v={n}") 13 | autogenerated = False 14 | -------------------------------------------------------------------------------- /tests/functional/status_test.py: -------------------------------------------------------------------------------- 1 | class TestStatus: 2 | def test_get_front_page(self, test_app): 3 | response = test_app.get("/_status", status=200) 4 | 5 | assert response.content_type == "application/json" 6 | assert response.json == {"status": "okay"} 7 | assert ( 8 | response.headers["Cache-Control"] 9 | == "max-age=0, must-revalidate, no-cache, no-store" 10 | ) 11 | -------------------------------------------------------------------------------- /via/static/images/icons/play.svg: -------------------------------------------------------------------------------- 1 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /via/static/images/icons/up.svg: -------------------------------------------------------------------------------- 1 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /via/static/images/icons/down.svg: -------------------------------------------------------------------------------- 1 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /requirements/prod.in: -------------------------------------------------------------------------------- 1 | pip==25.3 2 | pyramid 3 | gunicorn 4 | newrelic 5 | sqlalchemy 6 | psycopg2 7 | alembic 8 | pyramid-tm 9 | zope.sqlalchemy 10 | checkmatelib 11 | h-assets 12 | sentry-sdk 13 | h-pyramid-sentry 14 | h-vialib 15 | importlib_resources 16 | pyjwt 17 | pyramid-exclog 18 | pyramid-jinja2 19 | pyramid-sanity 20 | pyramid-services 21 | requests 22 | whitenoise 23 | google-auth-oauthlib 24 | marshmallow 25 | webargs 26 | webvtt-py 27 | -------------------------------------------------------------------------------- /tests/functional/conftest.py: -------------------------------------------------------------------------------- 1 | import httpretty 2 | import pytest 3 | import webtest 4 | 5 | from via.app import create_app 6 | 7 | 8 | @pytest.fixture 9 | def test_app(pyramid_settings): 10 | return webtest.TestApp(create_app(None, **pyramid_settings)) 11 | 12 | 13 | @pytest.fixture 14 | def checkmate_pass(): 15 | httpretty.register_uri( 16 | httpretty.GET, "http://localhost:9099/api/check", status=204, body="" 17 | ) 18 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/toolbarButton-presentationMode.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/toolbarButton-zoomIn.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /tests/functional/api/view_pdf_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from tests.conftest import assert_cache_control 4 | 5 | 6 | class TestViewPDFAPI: 7 | @pytest.mark.usefixtures("checkmate_pass") 8 | def test_caching_is_disabled(self, test_app): 9 | response = test_app.get("/pdf?url=http://example.com/foo.pdf") 10 | 11 | assert_cache_control( 12 | response.headers, ["max-age=0", "must-revalidate", "no-cache", "no-store"] 13 | ) 14 | -------------------------------------------------------------------------------- /via/migrations/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | """ 6 | from alembic import op 7 | import sqlalchemy as sa 8 | ${imports if imports else ""} 9 | 10 | revision = ${repr(up_revision)} 11 | down_revision = ${repr(down_revision)} 12 | 13 | 14 | def upgrade() -> None: 15 | ${upgrades if upgrades else "pass"} 16 | 17 | 18 | def downgrade() -> None: 19 | ${downgrades if downgrades else "pass"} 20 | -------------------------------------------------------------------------------- /tests/unit/via/services/fixtures/google_404_file_not_found.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "errors": [ 4 | { 5 | "domain": "global", 6 | "reason": "notFound", 7 | "message": "File not found: 0ByDOWc.", 8 | "locationType": "parameter", 9 | "location": "fileId" 10 | } 11 | ], 12 | "code": 404, 13 | "message": "File not found: 0ByDOWc." 14 | } 15 | } -------------------------------------------------------------------------------- /via/static/images/icons/pause.svg: -------------------------------------------------------------------------------- 1 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /.cookiecutter/includes/.github/dependabot/npm/tail.yml: -------------------------------------------------------------------------------- 1 | groups: 2 | babel: 3 | patterns: 4 | - '@babel/*' 5 | eslint: 6 | patterns: 7 | - 'eslint*' 8 | - 'typescript-eslint' 9 | rollup: 10 | patterns: 11 | - 'rollup' 12 | - '@rollup/*' 13 | sentry: 14 | patterns: 15 | - '@sentry/*' 16 | typescript-types: 17 | patterns: 18 | - '@types/*' 19 | vitest: 20 | patterns: 21 | - 'vitest' 22 | - '@vitest/*' 23 | -------------------------------------------------------------------------------- /via/sentry_filters.py: -------------------------------------------------------------------------------- 1 | """Functions for filtering out events and log messages we don't want to report to Sentry.""" 2 | 3 | from sentry_sdk.types import Hint, Log 4 | 5 | 6 | def sentry_before_send_log(log: Log, _hint: Hint) -> Log | None: 7 | """Filter out log messages that we don't want to send to Sentry Logs.""" 8 | 9 | if log.get("attributes", {}).get("logger.name") == "gunicorn.access": 10 | return None 11 | 12 | return log 13 | 14 | 15 | SENTRY_FILTERS = [] 16 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/findbarButton-next.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/findbarButton-previous.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /tests/unit/via/services/fixtures/google_403_malicious.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "errors": [ 4 | { 5 | "domain": "global", 6 | "reason": "cannotDownloadAbusiveFile", 7 | "message": "This file has been identified as malware or spam and cannot be downloaded." 8 | } 9 | ], 10 | "code": 403, 11 | "message": "This file has been identified as malware or spam and cannot be downloaded." 12 | } 13 | } -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/annotation-insert.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/toolbarButton-search.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/annotation-check.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /tests/unit/via/views/webargs_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from marshmallow.exceptions import ValidationError as MarshmallowValidationError 3 | 4 | from via.views.webargs import handle_error 5 | 6 | 7 | def test_handle_error(): 8 | validation_error = MarshmallowValidationError("Test error") 9 | 10 | with pytest.raises(MarshmallowValidationError) as exc_info: 11 | handle_error(validation_error) 12 | 13 | assert exc_info.value.status_int == 400 14 | assert exc_info.value == validation_error 15 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/toolbarButton-viewOutline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/annotation-newparagraph.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /.cookiecutter/includes/tox/setenv: -------------------------------------------------------------------------------- 1 | dev: CHECKMATE_URL = http://localhost:9099 2 | dev: CHECKMATE_IGNORE_REASONS = {env:CHECKMATE_IGNORE_REASONS:publisher-blocked} 3 | dev: CHECKMATE_ALLOW_ALL = {env:CHECKMATE_ALLOW_ALL:true} 4 | dev: NGINX_SECURE_LINK_SECRET = not_a_secret 5 | dev: VIA_SECRET = not_a_secret 6 | dev: CHECKMATE_API_KEY = dev_api_key 7 | dev: ENABLE_FRONT_PAGE = {env:ENABLE_FRONT_PAGE:true} 8 | dev: DATA_DIRECTORY = .devdata/ 9 | dev: YOUTUBE_TRANSCRIPTS = {env:YOUTUBE_TRANSCRIPTS:true} 10 | dev: API_JWT_SECRET = secret 11 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/secondaryToolbarButton-scrollWrapped.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | coverage 3 | node_modules 4 | yarn-error.log 5 | .coverage* 6 | .tox 7 | *.egg-info 8 | *.pyc 9 | supervisord.log 10 | supervisord.pid 11 | .DS_Store 12 | .devdata* 13 | .eslintcache 14 | *.tsbuildinfo 15 | .vscode 16 | via/static/**/*.gz 17 | build.tar 18 | 19 | # See https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored. 20 | # This is the "If you're not using Zero-Installs" list. 21 | .pnp.* 22 | .yarn/* 23 | !.yarn/patches 24 | !.yarn/plugins 25 | !.yarn/releases 26 | !.yarn/sdks 27 | !.yarn/versions 28 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/secondaryToolbarButton-selectTool.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /via/static/scripts/types/css-custom-highlights.d.ts: -------------------------------------------------------------------------------- 1 | // Types for the CSS Custom Highlight API. 2 | // 3 | // See https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API. 4 | // 5 | // TypeScript's built-in types includes incomplete versions of these types. 6 | 7 | declare interface Highlight { 8 | add(r: Range): void; 9 | delete(r: Range): void; 10 | } 11 | 12 | interface HighlightRegistry { 13 | delete(name: string): void; 14 | get(name: string): Highlight | undefined; 15 | set(name: string, highlight: Highlight): void; 16 | } 17 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/secondaryToolbarButton-rotateCw.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/toolbarButton-pageDown.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /via/static/scripts/setup-tests.js: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai'; 2 | import { configure } from 'enzyme'; 3 | import { Adapter } from 'enzyme-adapter-preact-pure'; 4 | import 'preact/debug'; 5 | import sinon from 'sinon'; 6 | 7 | // Expose sinon assertions and add extra assert methods. 8 | sinon.assert.expose(assert, { prefix: null }); 9 | 10 | // Expose these globally 11 | globalThis.assert = assert; 12 | globalThis.sinon = sinon; 13 | globalThis.context ??= globalThis.describe; 14 | 15 | // Configure Enzyme for UI tests. 16 | configure({ adapter: new Adapter() }); 17 | -------------------------------------------------------------------------------- /conf/nginx/includes/resolver.conf: -------------------------------------------------------------------------------- 1 | # Configure the DNS that nginx uses to connect to the servers it's proxying. 2 | # http://nginx.org/en/docs/http/ngx_http_core_module.html#resolver 3 | # Here we are setting the value of the resolver to the AWS VPC DNS server. 4 | # Each AWS VPC has a DNS server available at the CIDR base + 2 address. 5 | # 169.254.169.253 is mapped to what ever that value is meaning we can deploy 6 | # in other AWS VPCs without updating the resolver value. The only requirement 7 | # is to have DNS support enabled for the VPC. 8 | 9 | resolver 169.254.169.253 ipv6=off; -------------------------------------------------------------------------------- /via/static/scripts/video_player/components/TranscriptError.tsx: -------------------------------------------------------------------------------- 1 | import { Callout, CautionIcon } from '@hypothesis/frontend-shared'; 2 | 3 | import type { APIError } from '../utils/api'; 4 | 5 | export type TranscriptErrorProps = { 6 | error: APIError; 7 | }; 8 | 9 | export default function TranscriptError({ error }: TranscriptErrorProps) { 10 | return ( 11 | 12 |

Unable to load transcript

13 |

{error.error?.title ?? error.message}

14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/toolbarButton-pageUp.svg: -------------------------------------------------------------------------------- 1 | 4 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /.cookiecutter/includes/conf/supervisord-dev.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "programs": { 3 | "init_db": { 4 | "command": "python3 -m via.scripts.init_db --create --stamp", 5 | "startsecs": "0" 6 | }, 7 | "web": { 8 | "command": "newrelic-admin run-program gunicorn --paste conf/development.ini --config conf/gunicorn-dev.conf.py" 9 | }, 10 | "nginx": { 11 | "command": "docker compose run --rm --service-ports nginx-proxy", 12 | "stopsignal": "TERM" 13 | }, 14 | "assets": { 15 | "command": "node_modules/.bin/gulp watch" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /conf/nginx/dev_host_bridge.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Write the network of the host to the /etc/hosts file if "host.docker.internal" 4 | # is not supported on the system. 5 | # From: https://dev.to/bufferings/access-host-from-a-docker-container-4099 6 | 7 | # Check if we are on a supported system 8 | HOST_DOMAIN="host.docker.internal" 9 | ping -q -c1 $HOST_DOMAIN > /dev/null 2>&1 10 | 11 | # If not, then write find our IP and map "host.docker.internal" to it 12 | if [ $? -ne 0 ]; then 13 | HOST_IP=$(ip route | awk 'NR==1 {print $3}') 14 | echo -e "$HOST_IP\t$HOST_DOMAIN" >> /etc/hosts 15 | fi -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/toolbarButton-secondaryToolbarToggle.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /bin/make_python: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Ensure that each version of Python in the .python_version file is installed 4 | # in pyenv and that tox is installed within each version of Python. 5 | set -euo pipefail 6 | 7 | if [ -n "${CI+x}" ]; then exit; fi 8 | 9 | pyenv_root=$(pyenv root) 10 | 11 | for python_version in 3.11.9; do 12 | bin_dir=$pyenv_root/versions/$python_version/bin 13 | if [ ! -f "$bin_dir"/tox ]; then 14 | pyenv install --skip-existing "$python_version" 15 | "$bin_dir"/pip install --disable-pip-version-check 'tox<4' 16 | pyenv rehash 17 | fi 18 | done 19 | -------------------------------------------------------------------------------- /conf/nginx/includes/app_upstream.conf: -------------------------------------------------------------------------------- 1 | # Define the "web" server (the Via Gunicorn/Pyramid app) for proxying to with: 2 | # 3 | # proxy_pass http://web; 4 | # 5 | # http://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream 6 | 7 | upstream web { 8 | # We set fail_timeout=0 so that Gunicorn isn't considered unavailable if a 9 | # single request fails (e.g. if Gunicorn kills a worker for taking too long 10 | # to handle a single request). 11 | # http://nginx.org/en/docs/http/ngx_http_upstream_module.html#server 12 | server unix:/tmp/gunicorn-web.sock fail_timeout=0; 13 | } 14 | -------------------------------------------------------------------------------- /via/static/images/icons/sync.svg: -------------------------------------------------------------------------------- 1 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /via/static/js/pdfjs-init.min.js: -------------------------------------------------------------------------------- 1 | "use strict";try{parent.document.addEventListener("webviewerloaded",onViewerLoaded)}catch(err){document.addEventListener("webviewerloaded",onViewerLoaded)}function onViewerLoaded(){PDFViewerApplicationOptions.set("defaultUrl","");const proxyPdfUrl=window.PROXY_PDF_URL,pdfUrl=window.PDF_URL,clientEmbedUrl=window.CLIENT_EMBED_URL,app=PDFViewerApplication;app.initializedPromise.then((()=>{PDFViewerApplicationOptions.set("disableRange",!0);const embedScript=document.createElement("script");embedScript.src=clientEmbedUrl,document.body.appendChild(embedScript),app.open({url:proxyPdfUrl,originalUrl:pdfUrl})}))} 2 | -------------------------------------------------------------------------------- /via/static/scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowSyntheticDefaultImports": true, 5 | "checkJs": true, 6 | "lib": ["es2021", "dom"], 7 | "jsx": "react-jsx", 8 | "jsxImportSource": "preact", 9 | "module": "commonjs", 10 | "noEmit": true, 11 | "strict": true, 12 | "target": "ES2020", 13 | "useUnknownInCatchVariables": false 14 | }, 15 | "include": ["**/*.js", "**/*.ts", "**/*.tsx"], 16 | "exclude": [ 17 | // Tests and test infrastructure 18 | "**/test/*.js", 19 | "setup-tests.js", 20 | "test-util/*.js" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /via/models/transcript.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import UniqueConstraint 2 | from sqlalchemy.dialects.postgresql import JSONB 3 | from sqlalchemy.orm import Mapped, mapped_column 4 | 5 | from via.db import Base 6 | from via.models._mixins import AutoincrementingIntegerIDMixin, CreatedUpdatedMixin 7 | 8 | 9 | class Transcript(AutoincrementingIntegerIDMixin, CreatedUpdatedMixin, Base): 10 | __tablename__ = "transcript" 11 | __table_args__ = (UniqueConstraint("video_id", "transcript_id"),) 12 | 13 | video_id: Mapped[str] 14 | transcript_id: Mapped[str] 15 | transcript: Mapped[list] = mapped_column(JSONB, repr=False) 16 | -------------------------------------------------------------------------------- /via/tweens.py: -------------------------------------------------------------------------------- 1 | def robots_tween_factory(handler, _registry): 2 | def robots_tween(request): 3 | response = handler(request) 4 | 5 | # If the view hasn't set its own X-Robots-Tag header then set one that 6 | # tells Google (and most other crawlers) not to index the page and not 7 | # to follow links on the page. 8 | # 9 | # https://developers.google.com/search/reference/robots_meta_tag 10 | if "X-Robots-Tag" not in response.headers: 11 | response.headers["X-Robots-Tag"] = "noindex, nofollow" 12 | 13 | return response 14 | 15 | return robots_tween 16 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/toolbarButton-currentOutlineItem.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/toolbarButton-print.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/secondaryToolbarButton-documentProperties.svg: -------------------------------------------------------------------------------- 1 | 4 | 6 | 8 | 9 | 11 | 12 | 14 | 15 | -------------------------------------------------------------------------------- /tests/common/requests_exceptions.py: -------------------------------------------------------------------------------- 1 | import json 2 | from io import BytesIO 3 | 4 | from requests import Response 5 | 6 | 7 | def make_requests_exception( 8 | error_class, status_code, json_data=None, raw_data=None, **response_kwargs 9 | ): 10 | response = Response() 11 | response.status_code = status_code 12 | 13 | for key, value in response_kwargs.items(): 14 | setattr(response, key, value) 15 | 16 | if raw_data: 17 | response.raw = BytesIO(raw_data.encode("utf-8")) 18 | 19 | elif json_data: 20 | response.raw = BytesIO(json.dumps(json_data).encode("utf-8")) 21 | 22 | return error_class(response=response) 23 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/toolbarButton-download.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /conf/alembic.ini: -------------------------------------------------------------------------------- 1 | [alembic] 2 | script_location = via/migrations 3 | 4 | [loggers] 5 | keys = root,sqlalchemy,alembic 6 | 7 | [handlers] 8 | keys = console 9 | 10 | [formatters] 11 | keys = generic 12 | 13 | [logger_root] 14 | level = INFO 15 | handlers = console 16 | qualname = 17 | 18 | [logger_sqlalchemy] 19 | level = WARN 20 | handlers = 21 | qualname = sqlalchemy.engine 22 | 23 | [logger_alembic] 24 | level = INFO 25 | handlers = 26 | qualname = alembic 27 | 28 | [handler_console] 29 | class = StreamHandler 30 | args = (sys.stderr,) 31 | level = NOTSET 32 | formatter = generic 33 | 34 | [formatter_generic] 35 | format = %(levelname)-5.5s [%(name)s] %(message)s 36 | datefmt = %H:%M:%S 37 | -------------------------------------------------------------------------------- /conf/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | environment=PYTHONUNBUFFERED="1" 4 | logfile=/dev/null 5 | logfile_maxbytes=0 6 | 7 | [program:nginx] 8 | command=nginx 9 | stdout_events_enabled=true 10 | stderr_events_enabled=true 11 | stdout_logfile=NONE 12 | stderr_logfile=NONE 13 | 14 | [program:web] 15 | command=newrelic-admin run-program gunicorn --paste conf/production.ini --config conf/gunicorn.conf.py 16 | stdout_events_enabled=true 17 | stderr_events_enabled=true 18 | stdout_logfile=NONE 19 | stderr_logfile=NONE 20 | 21 | [eventlistener:logger] 22 | command=bin/logger 23 | buffer_size=1024 24 | events=PROCESS_LOG 25 | stderr_logfile=/dev/fd/1 26 | stderr_logfile_maxbytes=0 27 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/toolbarButton-viewThumbnail.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/toolbarButton-sidebarToggle.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/toolbarButton-openFile.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /tests/unit/via/views/api/transcript_test.py: -------------------------------------------------------------------------------- 1 | from via.views.api.transcript import get_transcript 2 | 3 | 4 | class TestGetTranscript: 5 | def test_it(self, pyramid_request, transcript_service): 6 | url = "https://example.com/transcript.vtt" 7 | pyramid_request.params["url"] = url 8 | 9 | response = get_transcript(pyramid_request) 10 | 11 | transcript_service.get_transcript.assert_called_once_with(url) 12 | assert response == { 13 | "data": { 14 | "type": "transcripts", 15 | "attributes": { 16 | "segments": transcript_service.get_transcript.return_value 17 | }, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/slack.yml: -------------------------------------------------------------------------------- 1 | name: Slack 2 | on: 3 | workflow_run: 4 | workflows: [CI, Deploy] 5 | types: [completed] 6 | branches: [main] 7 | jobs: 8 | on-failure: 9 | if: ${{ github.event.workflow_run.conclusion == 'failure' }} 10 | name: Post to Slack 11 | uses: hypothesis/workflows/.github/workflows/slack.yml@main 12 | with: 13 | payload: | 14 | channel: "C4K6M7P5E" 15 | markdown_text: "Failed `${{ github.event.workflow_run.path }}` run (attempt ${{ github.event.workflow_run.run_attempt }}) on `${{ github.event.workflow_run.head_branch }}` in `${{ github.event.workflow_run.repository.full_name }}`: ${{ github.event.workflow_run.html_url }}" 16 | secrets: inherit 17 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/secondaryToolbarButton-spreadOdd.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/toolbarButton-viewLayers.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /via/pshell.py: -------------------------------------------------------------------------------- 1 | from h_testkit import set_factoryboy_sqlalchemy_session # type: ignore # noqa: PGH003 2 | 3 | from tests import factories 4 | from via import models 5 | 6 | 7 | def setup(env): 8 | env["tm"] = env["request"].tm 9 | env["tm"].__doc__ = "Active transaction manager (a transaction is already begun)." 10 | env["request"].tm.begin() 11 | 12 | env["db"] = env["request"].db 13 | env["db"].__doc__ = "Active DB session." 14 | 15 | env["m"] = env["models"] = models 16 | env["m"].__doc__ = "The via.models package." 17 | 18 | env["f"] = env["factories"] = factories 19 | env["f"].__doc__ = "The test factories for quickly creating objects." 20 | set_factoryboy_sqlalchemy_session(env["request"].db) 21 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-typescript", 4 | [ 5 | "@babel/preset-react", 6 | { 7 | "runtime": "automatic", 8 | "importSource": "preact" 9 | } 10 | ], 11 | 12 | // Compile JS for browser targets set by `browserslist` key in package.json. 13 | [ 14 | "@babel/preset-env", 15 | { 16 | "bugfixes": true 17 | } 18 | ] 19 | ], 20 | "env": { 21 | "development": { 22 | "presets": [ 23 | [ 24 | "@babel/preset-react", 25 | { 26 | "development": true, 27 | "runtime": "automatic", 28 | "importSource": "preact" 29 | } 30 | ] 31 | ] 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /via/static/scripts/video_player/components/test/HypothesisClient-test.js: -------------------------------------------------------------------------------- 1 | import { mount } from 'enzyme'; 2 | 3 | import HypothesisClient from '../HypothesisClient'; 4 | 5 | describe('HypothesisClient', () => { 6 | beforeEach(() => { 7 | delete window.hypothesisConfig; 8 | }); 9 | 10 | it('adds Hypothesis client and configuration to page', () => { 11 | const config = { openSidebar: true }; 12 | const wrapper = mount( 13 | , 14 | ); 15 | assert.isTrue(wrapper.exists('script[src="https://hypothes.is/embed.js"]')); 16 | 17 | assert.isFunction(window.hypothesisConfig); 18 | assert.equal(window.hypothesisConfig(), config); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /tests/unit/via/sentry_filters_test.py: -------------------------------------------------------------------------------- 1 | from unittest import mock 2 | 3 | import pytest 4 | 5 | from via.sentry_filters import sentry_before_send_log 6 | 7 | 8 | class TestSentryBeforeSendLog: 9 | @pytest.mark.parametrize( 10 | ("log", "should_be_filtered_out"), 11 | [ 12 | ({"attributes": {"logger.name": "gunicorn.access"}}, True), 13 | ({"attributes": {"logger.name": "foo"}}, False), 14 | ({}, False), 15 | ], 16 | ) 17 | def test_it(self, log, should_be_filtered_out): 18 | result = sentry_before_send_log(log, mock.sentinel.hint) 19 | 20 | if should_be_filtered_out: 21 | assert result is None 22 | else: 23 | assert result == log 24 | -------------------------------------------------------------------------------- /via/views/api/youtube.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from pyramid.view import view_config 4 | 5 | from via.services import YouTubeService 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | @view_config( 11 | route_name="api.youtube.transcript", 12 | request_method="GET", 13 | permission="api", 14 | renderer="json", 15 | ) 16 | def get_transcript(request): 17 | """Return the transcript of a given YouTube video.""" 18 | 19 | video_id = request.matchdict["video_id"] 20 | 21 | transcript = request.find_service(YouTubeService).get_transcript(video_id) 22 | 23 | return { 24 | "data": { 25 | "type": "transcripts", 26 | "id": video_id, 27 | "attributes": {"segments": transcript}, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/frontend.yml: -------------------------------------------------------------------------------- 1 | name: Frontend 2 | on: 3 | workflow_dispatch: 4 | workflow_call: 5 | jobs: 6 | Frontend: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v3 11 | 12 | - name: Cache the node_modules dir 13 | uses: actions/cache@v3 14 | with: 15 | path: node_modules 16 | key: ${{ runner.os }}-node_modules-${{ hashFiles('yarn.lock') }} 17 | 18 | - name: Install 19 | run: yarn install --frozen-lockfile && yarn playwright install chromium 20 | 21 | - name: Format 22 | run: yarn checkformatting 23 | 24 | - name: Lint 25 | run: yarn lint 26 | 27 | - name: Typecheck 28 | run: yarn typecheck 29 | 30 | - name: Test 31 | run: yarn test 32 | -------------------------------------------------------------------------------- /conf/development.ini: -------------------------------------------------------------------------------- 1 | [app:main] 2 | use = call:via.app:create_app 3 | debug = true 4 | 5 | nginx_server: http://localhost:9083 6 | client_embed_url: http://localhost:5000/embed.js 7 | via_html_url: http://localhost:9085/proxy 8 | 9 | [pshell] 10 | setup = via.pshell.setup 11 | 12 | [loggers] 13 | keys = root, via 14 | 15 | [handlers] 16 | keys = console 17 | 18 | [formatters] 19 | keys = generic 20 | 21 | [logger_root] 22 | level = INFO 23 | handlers = console 24 | 25 | [logger_via] 26 | level = DEBUG 27 | handlers = 28 | qualname = via 29 | 30 | [handler_console] 31 | class = StreamHandler 32 | args = (sys.stderr,) 33 | level = NOTSET 34 | formatter = generic 35 | 36 | [formatter_generic] 37 | format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s 38 | -------------------------------------------------------------------------------- /conf/production.ini: -------------------------------------------------------------------------------- 1 | [pipeline:main] 2 | pipeline: 3 | proxy-prefix 4 | via 5 | 6 | [app:via] 7 | use = call:via.app:create_app 8 | 9 | [filter:proxy-prefix] 10 | use: egg:PasteDeploy#prefix 11 | 12 | [loggers] 13 | keys = root, via, alembic 14 | 15 | [handlers] 16 | keys = console 17 | 18 | [formatters] 19 | keys = generic 20 | 21 | [logger_root] 22 | level = INFO 23 | handlers = console 24 | 25 | [logger_via] 26 | level = DEBUG 27 | handlers = 28 | qualname = via 29 | 30 | [logger_alembic] 31 | level = INFO 32 | handlers = 33 | qualname = alembic 34 | 35 | [handler_console] 36 | class = StreamHandler 37 | args = (sys.stderr,) 38 | level = NOTSET 39 | formatter = generic 40 | 41 | [formatter_generic] 42 | format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s 43 | -------------------------------------------------------------------------------- /via/models/_mixins.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from sqlalchemy import func 4 | from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column 5 | 6 | 7 | class AutoincrementingIntegerIDMixin(MappedAsDataclass): 8 | id: Mapped[int] = mapped_column( 9 | init=False, primary_key=True, autoincrement=True, sort_order=-100 10 | ) 11 | 12 | 13 | class CreatedUpdatedMixin(MappedAsDataclass): 14 | created: Mapped[datetime] = mapped_column( 15 | init=False, 16 | repr=False, 17 | server_default=func.now(), 18 | sort_order=-10, 19 | ) 20 | updated: Mapped[datetime] = mapped_column( 21 | init=False, 22 | repr=False, 23 | server_default=func.now(), 24 | onupdate=func.now(), 25 | sort_order=-10, 26 | ) 27 | -------------------------------------------------------------------------------- /via/static/scripts/video_player/utils/youtube.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Load the YouTube IFrame Player API. 3 | * 4 | * See https://developers.google.com/youtube/iframe_api_reference. 5 | */ 6 | export async function loadYouTubeIFrameAPI( 7 | scriptSrc = 'https://www.youtube.com/iframe_api', 8 | ): Promise { 9 | if (typeof window.YT?.Player === 'function') { 10 | return window.YT; 11 | } 12 | 13 | const scriptEl = document.createElement('script'); 14 | scriptEl.src = scriptSrc; 15 | 16 | return new Promise(resolve => { 17 | scriptEl.addEventListener('load', () => { 18 | // @ts-expect-error - The `ready` callback is missing from YT types. 19 | window.YT.ready(() => resolve(window.YT)); 20 | }); 21 | document.body.append(scriptEl); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /via/static/css/img/icons.svg: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/factories/transcript.py: -------------------------------------------------------------------------------- 1 | from factory import Sequence 2 | from factory.alchemy import SQLAlchemyModelFactory 3 | 4 | from via.models import Transcript 5 | 6 | 7 | class TranscriptFactory(SQLAlchemyModelFactory): 8 | class Meta: 9 | model = Transcript 10 | 11 | video_id = Sequence(lambda n: f"video_id_{n}") 12 | transcript_id = Sequence(lambda n: f"transcript_id_{n}") 13 | transcript = Sequence( 14 | lambda n: [ 15 | { 16 | "text": f"This is the first line of transcript {n}", 17 | "start": 0.0, 18 | "duration": 7.52, 19 | }, 20 | { 21 | "text": f"This is the second line of transcript {n}", 22 | "start": 5.6, 23 | "duration": 4.72, 24 | }, 25 | ] 26 | ) 27 | -------------------------------------------------------------------------------- /tests/unit/via/views/api/youtube_test.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import sentinel 2 | 3 | import pytest 4 | 5 | from via.views.api import youtube 6 | 7 | 8 | class TestGetTranscript: 9 | def test_it(self, pyramid_request, youtube_service): 10 | response = youtube.get_transcript(pyramid_request) 11 | 12 | youtube_service.get_transcript.assert_called_once_with(sentinel.video_id) 13 | assert response == { 14 | "data": { 15 | "type": "transcripts", 16 | "id": sentinel.video_id, 17 | "attributes": {"segments": youtube_service.get_transcript.return_value}, 18 | } 19 | } 20 | 21 | @pytest.fixture 22 | def pyramid_request(self, pyramid_request): 23 | pyramid_request.matchdict["video_id"] = sentinel.video_id 24 | return pyramid_request 25 | -------------------------------------------------------------------------------- /via/assets.py: -------------------------------------------------------------------------------- 1 | """View for serving static assets under `/assets`.""" 2 | 3 | import importlib_resources 4 | from h_assets import Environment, assets_view 5 | 6 | 7 | def includeme(config): 8 | # Auto reload asset manifest when it changes in development. 9 | auto_reload = config.registry.settings["dev"] 10 | via_files = importlib_resources.files("via") 11 | 12 | assets_env = Environment( 13 | assets_base_url="/assets", 14 | bundle_config_path=via_files / "assets.ini", 15 | manifest_path=via_files / "../build/manifest.json", 16 | auto_reload=auto_reload, 17 | ) 18 | 19 | # Store asset environment in registry for use in registering `asset_urls` 20 | # Jinja2 helper in `app.py`. 21 | config.registry["assets_env"] = assets_env 22 | 23 | config.add_view(route_name="assets", view=assets_view(assets_env)) 24 | -------------------------------------------------------------------------------- /via/static/scripts/video_player/components/icons/Up.tsx: -------------------------------------------------------------------------------- 1 | // This file was auto-generated using scripts/generate-icons.js 2 | import type { JSX } from 'preact'; 3 | 4 | export type UpIconProps = JSX.SVGAttributes; 5 | 6 | /** 7 | * Icon generated from up.svg 8 | */ 9 | export default function UpIcon(props: UpIconProps) { 10 | return ( 11 | 20 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /via/static/scripts/video_player/components/icons/Down.tsx: -------------------------------------------------------------------------------- 1 | // This file was auto-generated using scripts/generate-icons.js 2 | import type { JSX } from 'preact'; 3 | 4 | export type DownIconProps = JSX.SVGAttributes; 5 | 6 | /** 7 | * Icon generated from down.svg 8 | */ 9 | export default function DownIcon(props: DownIconProps) { 10 | return ( 11 | 20 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /via/static/scripts/video_player/components/icons/Play.tsx: -------------------------------------------------------------------------------- 1 | // This file was auto-generated using scripts/generate-icons.js 2 | import type { JSX } from 'preact'; 3 | 4 | export type PlayIconProps = JSX.SVGAttributes; 5 | 6 | /** 7 | * Icon generated from play.svg 8 | */ 9 | export default function PlayIcon(props: PlayIconProps) { 10 | return ( 11 | 20 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /via/views/api/transcript.py: -------------------------------------------------------------------------------- 1 | import marshmallow 2 | from pyramid.view import view_config 3 | from webargs import fields 4 | from webargs.pyramidparser import use_kwargs 5 | 6 | from via.services import TranscriptService 7 | 8 | 9 | @view_config( 10 | route_name="api.video.transcript", 11 | request_method="GET", 12 | permission="api", 13 | renderer="json", 14 | ) 15 | @use_kwargs( 16 | {"url": fields.Url(required=True)}, location="query", unknown=marshmallow.EXCLUDE 17 | ) 18 | def get_transcript(request, url: str): 19 | """Fetch a video transcript. 20 | 21 | Fetches a transcript in WebVTT or SRT format and returns it as JSON. 22 | """ 23 | 24 | transcript = request.find_service(TranscriptService).get_transcript(url) 25 | 26 | return { 27 | "data": { 28 | "type": "transcripts", 29 | "attributes": {"segments": transcript}, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/keepalive.yml: -------------------------------------------------------------------------------- 1 | # Prevent scheduled workflows from being disabled due to inactivity. 2 | # 3 | # GitHub disables scheduled workflows after 60 days of repo inactivity: 4 | # 5 | # > Warning: To prevent unnecessary workflow runs, scheduled workflows may be 6 | # > disabled automatically. 7 | # > ... In a public repository, scheduled workflows are automatically disabled 8 | # > when no repository activity has occurred in 60 days. 9 | # 10 | # https://docs.github.com/en/actions/using-workflows/disabling-and-enabling-a-workflow 11 | # 12 | # This keep-alive workflow triggers whenever one of the scheduled workflows 13 | # listed below completes and prevents that scheduled workflow from being 14 | # disabled. 15 | name: Keepalive 16 | on: 17 | workflow_run: 18 | workflows: [CI] 19 | types: [completed] 20 | branches: [main] 21 | jobs: 22 | Keepalive: 23 | uses: hypothesis/workflows/.github/workflows/keepalive.yml@main 24 | -------------------------------------------------------------------------------- /via/static/scripts/video_player/components/icons/Pause.tsx: -------------------------------------------------------------------------------- 1 | // This file was auto-generated using scripts/generate-icons.js 2 | import type { JSX } from 'preact'; 3 | 4 | export type PauseIconProps = JSX.SVGAttributes; 5 | 6 | /** 7 | * Icon generated from pause.svg 8 | */ 9 | export default function PauseIcon(props: PauseIconProps) { 10 | return ( 11 | 20 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /requirements/build.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.11 3 | # by the following command: 4 | # 5 | # pip-compile --allow-unsafe requirements/build.in 6 | # 7 | build==1.0.3 8 | # via pip-tools 9 | click==8.1.7 10 | # via pip-tools 11 | importlib-metadata==7.0.1 12 | # via pip-sync-faster 13 | packaging==23.2 14 | # via build 15 | pip-sync-faster==0.0.5 16 | # via -r build.in 17 | pip-tools==7.5.2 18 | # via 19 | # -r build.in 20 | # pip-sync-faster 21 | pyproject-hooks==1.0.0 22 | # via 23 | # build 24 | # pip-tools 25 | wheel==0.42.0 26 | # via pip-tools 27 | whitenoise==6.11.0 28 | # via -r build.in 29 | zipp==3.19.1 30 | # via importlib-metadata 31 | 32 | # The following packages are considered to be unsafe in a requirements file: 33 | pip==25.3 34 | # via 35 | # -r build.in 36 | # pip-tools 37 | setuptools==78.1.1 38 | # via pip-tools 39 | -------------------------------------------------------------------------------- /frontend.mk: -------------------------------------------------------------------------------- 1 | node_modules/.uptodate: package.json yarn.lock 2 | yarn install 3 | yarn playwright install chromium 4 | @touch $@ 5 | 6 | dev: node_modules/.uptodate 7 | 8 | .PHONY: build 9 | $(call help,make build,"prepare the build files") 10 | build: python node_modules/.uptodate 11 | @tox -qe build 12 | 13 | .PHONY: update-pdfjs 14 | $(call help,make update-pdfjs,"update our copy of PDF.js") 15 | update-pdfjs: python 16 | @tox -qe updatepdfjs 17 | 18 | .PHONY: frontend-lint 19 | $(call help,make frontend-lint,"lint the frontend code") 20 | frontend-lint: node_modules/.uptodate 21 | @yarn checkformatting 22 | @yarn lint 23 | @yarn typecheck 24 | 25 | .PHONY: frontend-format 26 | $(call help,make frontend-format,"format the frontend code") 27 | frontend-format: node_modules/.uptodate 28 | @yarn format 29 | 30 | .PHONY: frontend-test 31 | $(call help,make frontend-test,"run the frontend tests") 32 | frontend-test: node_modules/.uptodate 33 | @yarn test $(ARGS) 34 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/annotation-comment.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /via/views/proxy.py: -------------------------------------------------------------------------------- 1 | from pyramid.httpexceptions import HTTPGone 2 | from pyramid.view import view_config 3 | 4 | from via.services import URLDetailsService, ViaClientService, has_secure_url_token 5 | 6 | 7 | @view_config(route_name="static_fallback") 8 | def static_fallback(_context, _request): 9 | """Make sure we don't try to proxy out of date static content.""" 10 | 11 | raise HTTPGone("It appears you have requested out of date content") # noqa: EM101, TRY003 12 | 13 | 14 | @view_config( 15 | route_name="proxy", 16 | renderer="via:templates/proxy.html.jinja2", 17 | decorator=(has_secure_url_token,), 18 | ) 19 | def proxy(context, request): 20 | url = context.url_from_path() 21 | 22 | mime_type, _status_code = request.find_service(URLDetailsService).get_url_details( 23 | url 24 | ) 25 | 26 | return { 27 | "src": request.find_service(ViaClientService).url_for( 28 | url, mime_type, request.params 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /requirements/updatepdfjs.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.11 3 | # by the following command: 4 | # 5 | # pip-compile --allow-unsafe requirements/updatepdfjs.in 6 | # 7 | build==1.0.3 8 | # via pip-tools 9 | click==8.1.7 10 | # via pip-tools 11 | importlib-metadata==7.0.1 12 | # via pip-sync-faster 13 | importlib-resources==6.5.2 14 | # via -r updatepdfjs.in 15 | packaging==23.2 16 | # via build 17 | pip-sync-faster==0.0.5 18 | # via -r updatepdfjs.in 19 | pip-tools==7.5.2 20 | # via 21 | # -r updatepdfjs.in 22 | # pip-sync-faster 23 | pyproject-hooks==1.0.0 24 | # via 25 | # build 26 | # pip-tools 27 | wheel==0.42.0 28 | # via pip-tools 29 | zipp==3.19.1 30 | # via importlib-metadata 31 | 32 | # The following packages are considered to be unsafe in a requirements file: 33 | pip==25.3 34 | # via 35 | # -r updatepdfjs.in 36 | # pip-tools 37 | setuptools==78.1.1 38 | # via pip-tools 39 | -------------------------------------------------------------------------------- /requirements/format.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.11 3 | # by the following command: 4 | # 5 | # pip-compile --allow-unsafe requirements/format.in 6 | # 7 | build==1.0.3 8 | # via pip-tools 9 | click==8.1.7 10 | # via pip-tools 11 | importlib-metadata==7.0.1 12 | # via pip-sync-faster 13 | packaging==23.2 14 | # via build 15 | pip-sync-faster==0.0.5 16 | # via -r requirements/format.in 17 | pip-tools==7.5.2 18 | # via 19 | # -r requirements/format.in 20 | # pip-sync-faster 21 | pyproject-hooks==1.0.0 22 | # via 23 | # build 24 | # pip-tools 25 | ruff==0.14.7 26 | # via -r requirements/format.in 27 | wheel==0.42.0 28 | # via pip-tools 29 | zipp==3.19.1 30 | # via importlib-metadata 31 | 32 | # The following packages are considered to be unsafe in a requirements file: 33 | pip==25.3 34 | # via 35 | # -r requirements/format.in 36 | # pip-tools 37 | setuptools==78.1.1 38 | # via pip-tools 39 | -------------------------------------------------------------------------------- /requirements/coverage.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.11 3 | # by the following command: 4 | # 5 | # pip-compile --allow-unsafe requirements/coverage.in 6 | # 7 | build==1.0.3 8 | # via pip-tools 9 | click==8.1.7 10 | # via pip-tools 11 | coverage[toml]==7.12.0 12 | # via -r requirements/coverage.in 13 | importlib-metadata==7.0.1 14 | # via pip-sync-faster 15 | packaging==23.2 16 | # via build 17 | pip-sync-faster==0.0.5 18 | # via -r requirements/coverage.in 19 | pip-tools==7.5.2 20 | # via 21 | # -r requirements/coverage.in 22 | # pip-sync-faster 23 | pyproject-hooks==1.0.0 24 | # via 25 | # build 26 | # pip-tools 27 | wheel==0.42.0 28 | # via pip-tools 29 | zipp==3.19.1 30 | # via importlib-metadata 31 | 32 | # The following packages are considered to be unsafe in a requirements file: 33 | pip==25.3 34 | # via 35 | # -r requirements/coverage.in 36 | # pip-tools 37 | setuptools==78.1.1 38 | # via pip-tools 39 | -------------------------------------------------------------------------------- /via/migrations/versions/d4e3e1bf95eb_add_the_video_table.py: -------------------------------------------------------------------------------- 1 | """Add the video table. 2 | 3 | Revision ID: d4e3e1bf95eb 4 | Revises: 5 | """ 6 | 7 | import sqlalchemy as sa 8 | from alembic import op 9 | 10 | revision = "d4e3e1bf95eb" 11 | down_revision = "9a37efe13a91" 12 | 13 | 14 | def upgrade() -> None: 15 | op.create_table( 16 | "video", 17 | sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), 18 | sa.Column( 19 | "created", sa.DateTime(), server_default=sa.text("now()"), nullable=False 20 | ), 21 | sa.Column( 22 | "updated", sa.DateTime(), server_default=sa.text("now()"), nullable=False 23 | ), 24 | sa.Column("video_id", sa.String(), nullable=False), 25 | sa.Column("title", sa.String(), nullable=False), 26 | sa.PrimaryKeyConstraint("id", name=op.f("pk_video")), 27 | sa.UniqueConstraint("video_id", name=op.f("uq_video_video_id")), 28 | ) 29 | 30 | 31 | def downgrade() -> None: 32 | op.drop_table("video") 33 | -------------------------------------------------------------------------------- /requirements/checkformatting.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.11 3 | # by the following command: 4 | # 5 | # pip-compile --allow-unsafe requirements/checkformatting.in 6 | # 7 | build==1.0.3 8 | # via pip-tools 9 | click==8.1.7 10 | # via pip-tools 11 | importlib-metadata==7.0.1 12 | # via pip-sync-faster 13 | packaging==23.2 14 | # via build 15 | pip-sync-faster==0.0.5 16 | # via -r requirements/checkformatting.in 17 | pip-tools==7.5.2 18 | # via 19 | # -r requirements/checkformatting.in 20 | # pip-sync-faster 21 | pyproject-hooks==1.0.0 22 | # via 23 | # build 24 | # pip-tools 25 | ruff==0.14.7 26 | # via -r requirements/checkformatting.in 27 | wheel==0.42.0 28 | # via pip-tools 29 | zipp==3.19.1 30 | # via importlib-metadata 31 | 32 | # The following packages are considered to be unsafe in a requirements file: 33 | pip==25.3 34 | # via 35 | # -r requirements/checkformatting.in 36 | # pip-tools 37 | setuptools==78.1.1 38 | # via pip-tools 39 | -------------------------------------------------------------------------------- /via/static/scripts/test-util/video-player-fixtures.ts: -------------------------------------------------------------------------------- 1 | // Shared fixtures for video player app tests. 2 | 3 | /** 4 | * Configuration for video player app. 5 | */ 6 | export const videoPlayerConfig = { 7 | api: { 8 | transcript: { 9 | doc: 'Get the transcript of the current video', 10 | url: 'https://dummy-via.hypothes.is/api/youtube/test_video_id', 11 | method: 'GET', 12 | headers: { 13 | Authorization: 'Bearer FAKE_JWT_TOKEN', 14 | }, 15 | }, 16 | }, 17 | }; 18 | 19 | /** 20 | * Dummy response from transcript API endpoint. 21 | */ 22 | export const transcriptsAPIResponse = { 23 | data: { 24 | type: 'transcripts', 25 | id: 'test_video_id', 26 | attributes: { 27 | segments: [ 28 | { 29 | text: '[Music]', 30 | start: 0.0, 31 | duration: 7.52, 32 | }, 33 | { 34 | text: 'how many of you remember the first time', 35 | start: 5.6, 36 | duration: 4.72, 37 | }, 38 | ], 39 | }, 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /via/static/scripts/video_player/components/test/TranscriptError-test.js: -------------------------------------------------------------------------------- 1 | import { mount } from 'enzyme'; 2 | 3 | import { APIError } from '../../utils/api'; 4 | import TranscriptError from '../TranscriptError'; 5 | 6 | describe('TranscriptError', () => { 7 | it('displays just `Error.message` if there are no error details', () => { 8 | const wrapper = mount( 9 | , 10 | ); 11 | assert.equal( 12 | wrapper.text(), 13 | ['Unable to load transcript', 'Something went wrong'].join(''), 14 | ); 15 | }); 16 | 17 | it('displays `APIError.error.title` field if present', () => { 18 | const error = new APIError(404, { 19 | code: 'VideoNotFound', 20 | title: 'The video was not found', 21 | detail: 'Some long details here', 22 | }); 23 | const wrapper = mount(); 24 | assert.equal( 25 | wrapper.text(), 26 | ['Unable to load transcript', 'The video was not found'].join(''), 27 | ); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /via/static/vendor/pdfjs-2/web/images/secondaryToolbarButton-spreadEven.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /via/static/scripts/video_player/components/icons/Sync.tsx: -------------------------------------------------------------------------------- 1 | // This file was auto-generated using scripts/generate-icons.js 2 | import type { JSX } from 'preact'; 3 | 4 | export type SyncIconProps = JSX.SVGAttributes; 5 | 6 | /** 7 | * Icon generated from sync.svg 8 | */ 9 | export default function SyncIcon(props: SyncIconProps) { 10 | return ( 11 | 20 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /via/views/status.py: -------------------------------------------------------------------------------- 1 | from checkmatelib import CheckmateException 2 | from pyramid import view 3 | from sentry_sdk import capture_message 4 | 5 | from via.services import CheckmateService 6 | 7 | 8 | @view.view_config(route_name="status", renderer="json", http_cache=0) 9 | def status(request): 10 | body = {"status": "okay"} 11 | 12 | if "include-checkmate" in request.params: 13 | checkmate_service = request.find_service(CheckmateService) 14 | 15 | try: 16 | checkmate_service.check_url("https://example.com/") 17 | except CheckmateException: 18 | body["down"] = ["checkmate"] 19 | else: 20 | body["okay"] = ["checkmate"] 21 | 22 | # If any of the components checked above were down then report the 23 | # status check as a whole as being down. 24 | if body.get("down"): 25 | request.response.status_int = 500 26 | body["status"] = "down" 27 | 28 | if "sentry" in request.params: 29 | capture_message("Test message from Via's status view") 30 | 31 | return body 32 | -------------------------------------------------------------------------------- /via/static/scripts/video_player/components/HypothesisClient.tsx: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from 'preact/hooks'; 2 | 3 | export type HypothesisClientProps = { 4 | /** URL of the client's boot script. */ 5 | src?: string; 6 | 7 | /** 8 | * Configuration to pass to the Hypothesis client via the `hypothesisConfig` 9 | * global [1]. 10 | * 11 | * Note that changing this has no effect once the client is started and read 12 | * its configuration. 13 | * 14 | * [1] https://h.readthedocs.io/projects/client/en/latest/publishers/config.html#configuring-the-client-using-javascript 15 | */ 16 | config?: object; 17 | }; 18 | 19 | /** 20 | * A component which loads the Hypothesis client into the current page. 21 | */ 22 | export default function HypothesisClient({ 23 | src = 'https://hypothes.is/embed.js', 24 | config = {}, 25 | }: HypothesisClientProps) { 26 | // nb. Use a layout effect here so that variable is definitely set before 27 | // client's boot script executes. 28 | useLayoutEffect(() => { 29 | (window as any).hypothesisConfig = () => config; 30 | }, [config]); 31 | 32 | return 36 | 37 | {% for url in asset_urls("video_player_js") %} 38 | 39 | {% endfor %} 40 | 41 | 42 | -------------------------------------------------------------------------------- /conf/supervisord-dev.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | silent=true 4 | 5 | [program:init_db] 6 | command=python3 -m via.scripts.init_db --create --stamp 7 | stdout_events_enabled=true 8 | stderr_events_enabled=true 9 | stopsignal=KILL 10 | stopasgroup=true 11 | startsecs=0 12 | 13 | [program:web] 14 | command=newrelic-admin run-program gunicorn --paste conf/development.ini --config conf/gunicorn-dev.conf.py 15 | stdout_events_enabled=true 16 | stderr_events_enabled=true 17 | stopsignal=KILL 18 | stopasgroup=true 19 | 20 | [program:nginx] 21 | command=docker compose run --rm --service-ports nginx-proxy 22 | stdout_events_enabled=true 23 | stderr_events_enabled=true 24 | stopsignal=TERM 25 | stopasgroup=true 26 | 27 | [program:assets] 28 | command=node_modules/.bin/gulp watch 29 | stdout_events_enabled=true 30 | stderr_events_enabled=true 31 | stopsignal=KILL 32 | stopasgroup=true 33 | 34 | [eventlistener:logger] 35 | command=bin/logger --dev 36 | buffer_size=100 37 | events=PROCESS_LOG 38 | stderr_logfile=/dev/fd/1 39 | stderr_logfile_maxbytes=0 40 | stdout_logfile=/dev/null 41 | 42 | [unix_http_server] 43 | file = .supervisor.sock 44 | 45 | [rpcinterface:supervisor] 46 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface 47 | 48 | [supervisorctl] 49 | serverurl = unix://.supervisor.sock 50 | prompt = via 51 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | day: "sunday" 8 | time: "00:00" 9 | timezone: "Europe/London" 10 | - package-ecosystem: "npm" 11 | directory: "/" 12 | schedule: 13 | interval: "monthly" 14 | day: "sunday" 15 | time: "00:00" 16 | timezone: "Europe/London" 17 | groups: 18 | babel: 19 | patterns: 20 | - '@babel/*' 21 | eslint: 22 | patterns: 23 | - 'eslint*' 24 | - 'typescript-eslint' 25 | rollup: 26 | patterns: 27 | - 'rollup' 28 | - '@rollup/*' 29 | sentry: 30 | patterns: 31 | - '@sentry/*' 32 | tailwind: 33 | patterns: 34 | - 'tailwindcss' 35 | - '@tailwindcss/*' 36 | typescript-types: 37 | patterns: 38 | - '@types/*' 39 | vitest: 40 | patterns: 41 | - 'vitest' 42 | - '@vitest/*' 43 | - package-ecosystem: "docker" 44 | directory: "/" 45 | schedule: 46 | interval: "monthly" 47 | day: "sunday" 48 | time: "00:00" 49 | timezone: "Europe/London" 50 | ignore: 51 | # Only send PRs for patch versions of Python. 52 | - dependency-name: "python" 53 | update-types: [ "version-update:semver-major", "version-update:semver-minor" ] 54 | -------------------------------------------------------------------------------- /tests/unit/conftest.py: -------------------------------------------------------------------------------- 1 | """A place to put fixture functions that are useful application-wide.""" 2 | 3 | import functools 4 | 5 | import pytest 6 | from pyramid import testing 7 | from pyramid.request import apply_request_extensions 8 | 9 | from tests.unit.services import * # noqa: F403 10 | from via.views import add_routes 11 | 12 | 13 | def autopatcher(request, target, **kwargs): 14 | """Patch and cleanup automatically. Wraps :py:func:`mock.patch`.""" 15 | options = {"autospec": True} 16 | options.update(kwargs) 17 | patcher = mock.patch(target, **options) # noqa: F405 18 | obj = patcher.start() 19 | request.addfinalizer(patcher.stop) 20 | return obj 21 | 22 | 23 | @pytest.fixture 24 | def patch(request): 25 | return functools.partial(autopatcher, request) 26 | 27 | 28 | @pytest.fixture 29 | def pyramid_config(pyramid_settings): 30 | with testing.testConfig(settings=pyramid_settings) as config: 31 | config.include("pyramid_jinja2") 32 | config.include("pyramid_services") 33 | add_routes(config) 34 | yield config 35 | 36 | 37 | @pytest.fixture 38 | def pyramid_request(db_session, pyramid_config): # noqa: ARG001 39 | pyramid_request = testing.DummyRequest() 40 | apply_request_extensions(pyramid_request) 41 | pyramid_request.db = db_session 42 | return pyramid_request 43 | -------------------------------------------------------------------------------- /conf/nginx/includes/errors.conf: -------------------------------------------------------------------------------- 1 | # Follow chains of multiple redirects (when the requested URL redirects to 2 | # another URL which in turn redirects to another). 3 | # 4 | # This is limited to following up to 10 redirects. 5 | # 6 | # recursive_error_pages is also needed to enable our 4xx and 5xx error handling 7 | # to kick in when the requested URL redirects to a URL that 4xx's or 5xx's. 8 | # 9 | # http://nginx.org/en/docs/http/ngx_http_core_module.html#recursive_error_pages 10 | recursive_error_pages on; 11 | 12 | # Intercept 3xx, 4xx and 5xx responses from the servers we're 13 | # proxying so we can optionally process them through error_page directives 14 | # below: http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_intercept_errors 15 | # 16 | # If no matching `error_page` is defined then the upstream error status and 17 | # response will be returned to the client. We currently do not modify 4xx or 5xx 18 | # responses so that we can debug interactions between Via and upstream services 19 | # in production. 20 | proxy_intercept_errors on; 21 | 22 | # Don't pass 3xx responses from the servers we're proxying back to 23 | # the browser. Instead process them through the @handle_redirect 24 | # location (which is defined in nginx.conf). 25 | # http://nginx.org/en/docs/http/ngx_http_core_module.html#error_page 26 | error_page 301 302 307 = @handle_redirect; 27 | -------------------------------------------------------------------------------- /via/static/scripts/video_player/config.ts: -------------------------------------------------------------------------------- 1 | import type { APIMethod } from './utils/api'; 2 | 3 | /** Directory of available API methods. */ 4 | export type APIIndex = { 5 | transcript: APIMethod; 6 | }; 7 | 8 | export type ConfigObject = { 9 | /** ID of the YouTube video to load. */ 10 | video_id?: string; 11 | 12 | /** Configuration for the Hypothesis client. */ 13 | client_config: object; 14 | 15 | /** URL of the Hypothesis client to load. */ 16 | client_src: string; 17 | 18 | /** Frontend player to use. */ 19 | player: 'youtube' | 'html-video'; 20 | 21 | /** URL for use with an HTML `