diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6be052cca..6f770bf4f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,5 @@ name: Build and Release +run-name: ${{ github.event.inputs.publish == 'true' && '[Prod]' || '[Dev]' }} Build ${{ github.run_number }} of branch ${{ github.ref_name }} by @${{ github.actor }} on: push: @@ -453,7 +454,7 @@ jobs: npm ci cd ../.. echo "Start Deploy..." - node ./dev/deploy-to-container/cli.js --branch ${{ github.ref_name }} --domain dev.ietf.org + node ./dev/deploy-to-container/cli.js --branch ${{ github.ref_name }} --domain dev.ietf.org --appversion ${{ env.PKG_VERSION }} --commit ${{ github.sha }} --ghrunid ${{ github.run_id }} - name: Cleanup old docker resources env: diff --git a/.github/workflows/dev-assets-sync-nightly.yml b/.github/workflows/dev-assets-sync-nightly.yml new file mode 100644 index 000000000..270274dce --- /dev/null +++ b/.github/workflows/dev-assets-sync-nightly.yml @@ -0,0 +1,61 @@ +# GITHUB ACTIONS - WORKFLOW + +# RSync the assets in the shared assets volume + +name: Nightly Dev Shared Assets Sync + +# Controls when the workflow will run +on: + # Run every night + schedule: + - cron: '0 1 * * *' + + # Run on app-rsync-extras.sh changes + push: + branches: + - main + paths: + - 'docker/scripts/app-rsync-extras.sh' + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + build: + name: Build Docker Image + runs-on: ubuntu-latest + if: ${{ github.event_name != 'schedule' }} + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Docker Build & Push + uses: docker/build-push-action@v3 + with: + context: . + file: dev/shared-assets-sync/Dockerfile + push: true + tags: ghcr.io/ietf-tools/datatracker-rsync-assets:latest + + sync: + name: Run assets rsync + if: ${{ always() }} + runs-on: dev-server + needs: [build] + steps: + - name: Run rsync + env: + DEBIAN_FRONTEND: noninteractive + run: | + docker pull ghcr.io/ietf-tools/datatracker-rsync-assets:latest + docker run --rm -v dt-assets:/assets ghcr.io/ietf-tools/datatracker-rsync-assets:latest + docker image prune -a -f diff --git a/.pnp.cjs b/.pnp.cjs index 375e2a21e..eb47b3380 100644 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -42,6 +42,8 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@fullcalendar/luxon2", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.11.3"],\ ["@fullcalendar/timegrid", "npm:5.11.3"],\ ["@fullcalendar/vue3", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.11.3"],\ + ["@parcel/optimizer-data-url", "npm:2.8.0"],\ + ["@parcel/transformer-inline-string", "npm:2.8.0"],\ ["@parcel/transformer-sass", "npm:2.8.0"],\ ["@popperjs/core", "npm:2.11.6"],\ ["@rollup/pluginutils", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.0.2"],\ @@ -52,7 +54,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["browser-fs-access", "npm:0.31.1"],\ ["browserlist", "npm:1.0.1"],\ ["c8", "npm:7.12.0"],\ - ["caniuse-lite", "npm:1.0.30001434"],\ + ["caniuse-lite", "npm:1.0.30001435"],\ ["d3", "npm:7.6.1"],\ ["eslint", "npm:8.28.0"],\ ["eslint-config-standard", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:17.0.0"],\ @@ -63,7 +65,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["eslint-plugin-promise", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:6.1.1"],\ ["eslint-plugin-vue", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:9.8.0"],\ ["file-saver", "npm:2.0.5"],\ - ["highcharts", "npm:10.3.1"],\ + ["highcharts", "npm:10.3.2"],\ ["html-validate", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:7.10.0"],\ ["jquery", "npm:3.6.1"],\ ["jquery-migrate", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:3.4.0"],\ @@ -72,14 +74,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["list.js", "npm:2.3.1"],\ ["lodash", "npm:4.17.21"],\ ["lodash-es", "npm:4.17.21"],\ - ["luxon", "npm:3.1.0"],\ + ["luxon", "npm:3.1.1"],\ ["moment", "npm:2.29.4"],\ ["moment-timezone", "npm:0.5.39"],\ ["ms", "npm:2.1.3"],\ ["murmurhash-js", "npm:1.0.0"],\ ["naive-ui", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.34.2"],\ ["parcel", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.8.0"],\ - ["pinia", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.0.26"],\ + ["pinia", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.0.27"],\ ["pinia-plugin-persist", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:1.0.0"],\ ["pug", "npm:3.0.2"],\ ["sass", "npm:1.56.1"],\ @@ -337,7 +339,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@fullcalendar/luxon2", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.11.3"],\ ["@fullcalendar/common", "npm:5.11.3"],\ ["@types/luxon", null],\ - ["luxon", "npm:3.1.0"],\ + ["luxon", "npm:3.1.1"],\ ["tslib", "npm:2.4.0"]\ ],\ "packagePeers": [\ @@ -1130,12 +1132,25 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@parcel/source-map", "npm:2.1.1"],\ ["@parcel/utils", "npm:2.8.0"],\ ["browserslist", "npm:4.20.3"],\ - ["lightningcss", "npm:1.16.1"],\ + ["lightningcss", "npm:1.17.1"],\ ["nullthrows", "npm:1.1.1"]\ ],\ "linkType": "HARD"\ }]\ ]],\ + ["@parcel/optimizer-data-url", [\ + ["npm:2.8.0", {\ + "packageLocation": "./.yarn/cache/@parcel-optimizer-data-url-npm-2.8.0-89a39d906e-998fb94cee.zip/node_modules/@parcel/optimizer-data-url/",\ + "packageDependencies": [\ + ["@parcel/optimizer-data-url", "npm:2.8.0"],\ + ["@parcel/plugin", "npm:2.8.0"],\ + ["@parcel/utils", "npm:2.8.0"],\ + ["isbinaryfile", "npm:4.0.10"],\ + ["mime", "npm:2.6.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@parcel/optimizer-htmlnano", [\ ["npm:2.8.0", {\ "packageLocation": "./.yarn/cache/@parcel-optimizer-htmlnano-npm-2.8.0-d2ead43d0c-68010e586b.zip/node_modules/@parcel/optimizer-htmlnano/",\ @@ -1480,7 +1495,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@parcel/source-map", "npm:2.1.1"],\ ["@parcel/utils", "npm:2.8.0"],\ ["browserslist", "npm:4.20.3"],\ - ["lightningcss", "npm:1.16.1"],\ + ["lightningcss", "npm:1.17.1"],\ ["nullthrows", "npm:1.1.1"]\ ],\ "linkType": "HARD"\ @@ -1529,6 +1544,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["@parcel/transformer-inline-string", [\ + ["npm:2.8.0", {\ + "packageLocation": "./.yarn/cache/@parcel-transformer-inline-string-npm-2.8.0-5fce2c90b8-e40616c55b.zip/node_modules/@parcel/transformer-inline-string/",\ + "packageDependencies": [\ + ["@parcel/transformer-inline-string", "npm:2.8.0"],\ + ["@parcel/plugin", "npm:2.8.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@parcel/transformer-js", [\ ["npm:2.8.0", {\ "packageLocation": "./.yarn/unplugged/@parcel-transformer-js-virtual-0a5c0b53bd/node_modules/@parcel/transformer-js/",\ @@ -2690,10 +2715,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "HARD"\ }],\ - ["npm:1.0.30001434", {\ - "packageLocation": "./.yarn/cache/caniuse-lite-npm-1.0.30001434-9c6ea57daf-7c9d2641e8.zip/node_modules/caniuse-lite/",\ + ["npm:1.0.30001435", {\ + "packageLocation": "./.yarn/cache/caniuse-lite-npm-1.0.30001435-7cebb35f0a-ec88b9c37f.zip/node_modules/caniuse-lite/",\ "packageDependencies": [\ - ["caniuse-lite", "npm:1.0.30001434"]\ + ["caniuse-lite", "npm:1.0.30001435"]\ ],\ "linkType": "HARD"\ }]\ @@ -4910,10 +4935,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["highcharts", [\ - ["npm:10.3.1", {\ - "packageLocation": "./.yarn/cache/highcharts-npm-10.3.1-e67a887ff6-8a1cf9a363.zip/node_modules/highcharts/",\ + ["npm:10.3.2", {\ + "packageLocation": "./.yarn/cache/highcharts-npm-10.3.2-1672942f09-43cb42b24c.zip/node_modules/highcharts/",\ "packageDependencies": [\ - ["highcharts", "npm:10.3.1"]\ + ["highcharts", "npm:10.3.2"]\ ],\ "linkType": "HARD"\ }]\ @@ -5456,6 +5481,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["isbinaryfile", [\ + ["npm:4.0.10", {\ + "packageLocation": "./.yarn/cache/isbinaryfile-npm-4.0.10-91d1251522-a6b28db7e2.zip/node_modules/isbinaryfile/",\ + "packageDependencies": [\ + ["isbinaryfile", "npm:4.0.10"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["isexe", [\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/isexe-npm-2.0.0-b58870bd2e-26bf6c5480.zip/node_modules/isexe/",\ @@ -5716,91 +5750,91 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["lightningcss", [\ - ["npm:1.16.1", {\ - "packageLocation": "./.yarn/cache/lightningcss-npm-1.16.1-dc51de6ab1-78ec1fa158.zip/node_modules/lightningcss/",\ + ["npm:1.17.1", {\ + "packageLocation": "./.yarn/cache/lightningcss-npm-1.17.1-7428f2d516-0bf9d5c932.zip/node_modules/lightningcss/",\ "packageDependencies": [\ - ["lightningcss", "npm:1.16.1"],\ + ["lightningcss", "npm:1.17.1"],\ ["detect-libc", "npm:1.0.3"],\ - ["lightningcss-darwin-arm64", "npm:1.16.1"],\ - ["lightningcss-darwin-x64", "npm:1.16.1"],\ - ["lightningcss-linux-arm-gnueabihf", "npm:1.16.1"],\ - ["lightningcss-linux-arm64-gnu", "npm:1.16.1"],\ - ["lightningcss-linux-arm64-musl", "npm:1.16.1"],\ - ["lightningcss-linux-x64-gnu", "npm:1.16.1"],\ - ["lightningcss-linux-x64-musl", "npm:1.16.1"],\ - ["lightningcss-win32-x64-msvc", "npm:1.16.1"]\ + ["lightningcss-darwin-arm64", "npm:1.17.1"],\ + ["lightningcss-darwin-x64", "npm:1.17.1"],\ + ["lightningcss-linux-arm-gnueabihf", "npm:1.17.1"],\ + ["lightningcss-linux-arm64-gnu", "npm:1.17.1"],\ + ["lightningcss-linux-arm64-musl", "npm:1.17.1"],\ + ["lightningcss-linux-x64-gnu", "npm:1.17.1"],\ + ["lightningcss-linux-x64-musl", "npm:1.17.1"],\ + ["lightningcss-win32-x64-msvc", "npm:1.17.1"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["lightningcss-darwin-arm64", [\ - ["npm:1.16.1", {\ - "packageLocation": "./.yarn/unplugged/lightningcss-darwin-arm64-npm-1.16.1-0a412810bd/node_modules/lightningcss-darwin-arm64/",\ + ["npm:1.17.1", {\ + "packageLocation": "./.yarn/unplugged/lightningcss-darwin-arm64-npm-1.17.1-a84f0d052c/node_modules/lightningcss-darwin-arm64/",\ "packageDependencies": [\ - ["lightningcss-darwin-arm64", "npm:1.16.1"]\ + ["lightningcss-darwin-arm64", "npm:1.17.1"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["lightningcss-darwin-x64", [\ - ["npm:1.16.1", {\ - "packageLocation": "./.yarn/unplugged/lightningcss-darwin-x64-npm-1.16.1-3f7b1b3519/node_modules/lightningcss-darwin-x64/",\ + ["npm:1.17.1", {\ + "packageLocation": "./.yarn/unplugged/lightningcss-darwin-x64-npm-1.17.1-131957b733/node_modules/lightningcss-darwin-x64/",\ "packageDependencies": [\ - ["lightningcss-darwin-x64", "npm:1.16.1"]\ + ["lightningcss-darwin-x64", "npm:1.17.1"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["lightningcss-linux-arm-gnueabihf", [\ - ["npm:1.16.1", {\ - "packageLocation": "./.yarn/unplugged/lightningcss-linux-arm-gnueabihf-npm-1.16.1-628363ec64/node_modules/lightningcss-linux-arm-gnueabihf/",\ + ["npm:1.17.1", {\ + "packageLocation": "./.yarn/unplugged/lightningcss-linux-arm-gnueabihf-npm-1.17.1-bbf7f4f213/node_modules/lightningcss-linux-arm-gnueabihf/",\ "packageDependencies": [\ - ["lightningcss-linux-arm-gnueabihf", "npm:1.16.1"]\ + ["lightningcss-linux-arm-gnueabihf", "npm:1.17.1"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["lightningcss-linux-arm64-gnu", [\ - ["npm:1.16.1", {\ - "packageLocation": "./.yarn/unplugged/lightningcss-linux-arm64-gnu-npm-1.16.1-3ca4dc231b/node_modules/lightningcss-linux-arm64-gnu/",\ + ["npm:1.17.1", {\ + "packageLocation": "./.yarn/unplugged/lightningcss-linux-arm64-gnu-npm-1.17.1-5b0e0aecb4/node_modules/lightningcss-linux-arm64-gnu/",\ "packageDependencies": [\ - ["lightningcss-linux-arm64-gnu", "npm:1.16.1"]\ + ["lightningcss-linux-arm64-gnu", "npm:1.17.1"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["lightningcss-linux-arm64-musl", [\ - ["npm:1.16.1", {\ - "packageLocation": "./.yarn/unplugged/lightningcss-linux-arm64-musl-npm-1.16.1-94c93845ed/node_modules/lightningcss-linux-arm64-musl/",\ + ["npm:1.17.1", {\ + "packageLocation": "./.yarn/unplugged/lightningcss-linux-arm64-musl-npm-1.17.1-4da73a58bf/node_modules/lightningcss-linux-arm64-musl/",\ "packageDependencies": [\ - ["lightningcss-linux-arm64-musl", "npm:1.16.1"]\ + ["lightningcss-linux-arm64-musl", "npm:1.17.1"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["lightningcss-linux-x64-gnu", [\ - ["npm:1.16.1", {\ - "packageLocation": "./.yarn/unplugged/lightningcss-linux-x64-gnu-npm-1.16.1-04529113fe/node_modules/lightningcss-linux-x64-gnu/",\ + ["npm:1.17.1", {\ + "packageLocation": "./.yarn/unplugged/lightningcss-linux-x64-gnu-npm-1.17.1-39d6988913/node_modules/lightningcss-linux-x64-gnu/",\ "packageDependencies": [\ - ["lightningcss-linux-x64-gnu", "npm:1.16.1"]\ + ["lightningcss-linux-x64-gnu", "npm:1.17.1"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["lightningcss-linux-x64-musl", [\ - ["npm:1.16.1", {\ - "packageLocation": "./.yarn/unplugged/lightningcss-linux-x64-musl-npm-1.16.1-01c07ec3c0/node_modules/lightningcss-linux-x64-musl/",\ + ["npm:1.17.1", {\ + "packageLocation": "./.yarn/unplugged/lightningcss-linux-x64-musl-npm-1.17.1-84311b8bf8/node_modules/lightningcss-linux-x64-musl/",\ "packageDependencies": [\ - ["lightningcss-linux-x64-musl", "npm:1.16.1"]\ + ["lightningcss-linux-x64-musl", "npm:1.17.1"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["lightningcss-win32-x64-msvc", [\ - ["npm:1.16.1", {\ - "packageLocation": "./.yarn/unplugged/lightningcss-win32-x64-msvc-npm-1.16.1-40af4d14b2/node_modules/lightningcss-win32-x64-msvc/",\ + ["npm:1.17.1", {\ + "packageLocation": "./.yarn/unplugged/lightningcss-win32-x64-msvc-npm-1.17.1-849d8d151b/node_modules/lightningcss-win32-x64-msvc/",\ "packageDependencies": [\ - ["lightningcss-win32-x64-msvc", "npm:1.16.1"]\ + ["lightningcss-win32-x64-msvc", "npm:1.17.1"]\ ],\ "linkType": "HARD"\ }]\ @@ -5918,10 +5952,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["luxon", [\ - ["npm:3.1.0", {\ - "packageLocation": "./.yarn/cache/luxon-npm-3.1.0-16e2508500-f8a850b759.zip/node_modules/luxon/",\ + ["npm:3.1.1", {\ + "packageLocation": "./.yarn/cache/luxon-npm-3.1.1-64fe977c1d-388fb35d3c.zip/node_modules/luxon/",\ "packageDependencies": [\ - ["luxon", "npm:3.1.0"]\ + ["luxon", "npm:3.1.1"]\ ],\ "linkType": "HARD"\ }]\ @@ -5987,6 +6021,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["mime", "npm:1.6.0"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:2.6.0", {\ + "packageLocation": "./.yarn/cache/mime-npm-2.6.0-88b89d8de0-1497ba7b9f.zip/node_modules/mime/",\ + "packageDependencies": [\ + ["mime", "npm:2.6.0"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["minimatch", [\ @@ -6659,17 +6700,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["pinia", [\ - ["npm:2.0.26", {\ - "packageLocation": "./.yarn/cache/pinia-npm-2.0.26-0d96417fac-0d38cc0efc.zip/node_modules/pinia/",\ + ["npm:2.0.27", {\ + "packageLocation": "./.yarn/cache/pinia-npm-2.0.27-3e0154e702-29c862ea43.zip/node_modules/pinia/",\ "packageDependencies": [\ - ["pinia", "npm:2.0.26"]\ + ["pinia", "npm:2.0.27"]\ ],\ "linkType": "SOFT"\ }],\ - ["virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.0.26", {\ - "packageLocation": "./.yarn/__virtual__/pinia-virtual-3c74e5a139/0/cache/pinia-npm-2.0.26-0d96417fac-0d38cc0efc.zip/node_modules/pinia/",\ + ["virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.0.27", {\ + "packageLocation": "./.yarn/__virtual__/pinia-virtual-0b7bfddb52/0/cache/pinia-npm-2.0.27-3e0154e702-29c862ea43.zip/node_modules/pinia/",\ "packageDependencies": [\ - ["pinia", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.0.26"],\ + ["pinia", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.0.27"],\ ["@types/typescript", null],\ ["@types/vue", null],\ ["@types/vue__composition-api", null],\ @@ -6677,7 +6718,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@vue/devtools-api", "npm:6.4.5"],\ ["typescript", null],\ ["vue", "npm:3.2.45"],\ - ["vue-demi", "virtual:3c74e5a1392a9d26efc27d5867a5220b1ab24b8bfb7c76fe2dac826f7e9d478b9c8eb69cc87331bb2ba20521466999017a02e9dca572946a787e2b4314602fca#npm:0.13.1"]\ + ["vue-demi", "virtual:0b7bfddb52b3cb488814806546397e52c62caef1815758033c8eac7ce386779ac52132e251ad567a19dde858cd2ed318ab2b52e9e258efd261b951e0d2160c16#npm:0.13.1"]\ ],\ "packagePeers": [\ "@types/typescript",\ @@ -6706,7 +6747,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@types/vue", null],\ ["@types/vue__composition-api", null],\ ["@vue/composition-api", null],\ - ["pinia", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.0.26"],\ + ["pinia", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.0.27"],\ ["vue", "npm:3.2.45"],\ ["vue-demi", "virtual:f56fcf19bbebc2ada1b28955da8cc216b1e9a569a1a7337d2d1926c1ebd1bc7a5bd91aedae1d05c15c8562f33caf7c59bd3020a667340f6bdc6a7b13fc2ba847#npm:0.12.5"]\ ],\ @@ -7232,6 +7273,8 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@fullcalendar/luxon2", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.11.3"],\ ["@fullcalendar/timegrid", "npm:5.11.3"],\ ["@fullcalendar/vue3", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.11.3"],\ + ["@parcel/optimizer-data-url", "npm:2.8.0"],\ + ["@parcel/transformer-inline-string", "npm:2.8.0"],\ ["@parcel/transformer-sass", "npm:2.8.0"],\ ["@popperjs/core", "npm:2.11.6"],\ ["@rollup/pluginutils", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.0.2"],\ @@ -7242,7 +7285,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["browser-fs-access", "npm:0.31.1"],\ ["browserlist", "npm:1.0.1"],\ ["c8", "npm:7.12.0"],\ - ["caniuse-lite", "npm:1.0.30001434"],\ + ["caniuse-lite", "npm:1.0.30001435"],\ ["d3", "npm:7.6.1"],\ ["eslint", "npm:8.28.0"],\ ["eslint-config-standard", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:17.0.0"],\ @@ -7253,7 +7296,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["eslint-plugin-promise", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:6.1.1"],\ ["eslint-plugin-vue", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:9.8.0"],\ ["file-saver", "npm:2.0.5"],\ - ["highcharts", "npm:10.3.1"],\ + ["highcharts", "npm:10.3.2"],\ ["html-validate", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:7.10.0"],\ ["jquery", "npm:3.6.1"],\ ["jquery-migrate", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:3.4.0"],\ @@ -7262,14 +7305,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["list.js", "npm:2.3.1"],\ ["lodash", "npm:4.17.21"],\ ["lodash-es", "npm:4.17.21"],\ - ["luxon", "npm:3.1.0"],\ + ["luxon", "npm:3.1.1"],\ ["moment", "npm:2.29.4"],\ ["moment-timezone", "npm:0.5.39"],\ ["ms", "npm:2.1.3"],\ ["murmurhash-js", "npm:1.0.0"],\ ["naive-ui", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.34.2"],\ ["parcel", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.8.0"],\ - ["pinia", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.0.26"],\ + ["pinia", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.0.27"],\ ["pinia-plugin-persist", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:1.0.0"],\ ["pug", "npm:3.0.2"],\ ["sass", "npm:1.56.1"],\ @@ -8159,16 +8202,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "SOFT"\ }],\ ["npm:0.13.1", {\ - "packageLocation": "./.yarn/unplugged/vue-demi-virtual-cfef5ecd67/node_modules/vue-demi/",\ + "packageLocation": "./.yarn/unplugged/vue-demi-virtual-615072ef29/node_modules/vue-demi/",\ "packageDependencies": [\ ["vue-demi", "npm:0.13.1"]\ ],\ "linkType": "SOFT"\ }],\ - ["virtual:3c74e5a1392a9d26efc27d5867a5220b1ab24b8bfb7c76fe2dac826f7e9d478b9c8eb69cc87331bb2ba20521466999017a02e9dca572946a787e2b4314602fca#npm:0.13.1", {\ - "packageLocation": "./.yarn/unplugged/vue-demi-virtual-cfef5ecd67/node_modules/vue-demi/",\ + ["virtual:0b7bfddb52b3cb488814806546397e52c62caef1815758033c8eac7ce386779ac52132e251ad567a19dde858cd2ed318ab2b52e9e258efd261b951e0d2160c16#npm:0.13.1", {\ + "packageLocation": "./.yarn/unplugged/vue-demi-virtual-615072ef29/node_modules/vue-demi/",\ "packageDependencies": [\ - ["vue-demi", "virtual:3c74e5a1392a9d26efc27d5867a5220b1ab24b8bfb7c76fe2dac826f7e9d478b9c8eb69cc87331bb2ba20521466999017a02e9dca572946a787e2b4314602fca#npm:0.13.1"],\ + ["vue-demi", "virtual:0b7bfddb52b3cb488814806546397e52c62caef1815758033c8eac7ce386779ac52132e251ad567a19dde858cd2ed318ab2b52e9e258efd261b951e0d2160c16#npm:0.13.1"],\ ["@types/vue", null],\ ["@types/vue__composition-api", null],\ ["@vue/composition-api", null],\ diff --git a/.yarn/cache/@parcel-optimizer-data-url-npm-2.8.0-89a39d906e-998fb94cee.zip b/.yarn/cache/@parcel-optimizer-data-url-npm-2.8.0-89a39d906e-998fb94cee.zip new file mode 100644 index 000000000..a0ad46637 Binary files /dev/null and b/.yarn/cache/@parcel-optimizer-data-url-npm-2.8.0-89a39d906e-998fb94cee.zip differ diff --git a/.yarn/cache/@parcel-transformer-inline-string-npm-2.8.0-5fce2c90b8-e40616c55b.zip b/.yarn/cache/@parcel-transformer-inline-string-npm-2.8.0-5fce2c90b8-e40616c55b.zip new file mode 100644 index 000000000..5598830cd Binary files /dev/null and b/.yarn/cache/@parcel-transformer-inline-string-npm-2.8.0-5fce2c90b8-e40616c55b.zip differ diff --git a/.yarn/cache/caniuse-lite-npm-1.0.30001434-9c6ea57daf-7c9d2641e8.zip b/.yarn/cache/caniuse-lite-npm-1.0.30001434-9c6ea57daf-7c9d2641e8.zip deleted file mode 100644 index daa4e7488..000000000 Binary files a/.yarn/cache/caniuse-lite-npm-1.0.30001434-9c6ea57daf-7c9d2641e8.zip and /dev/null differ diff --git a/.yarn/cache/caniuse-lite-npm-1.0.30001435-7cebb35f0a-ec88b9c37f.zip b/.yarn/cache/caniuse-lite-npm-1.0.30001435-7cebb35f0a-ec88b9c37f.zip new file mode 100644 index 000000000..9851a7ce7 Binary files /dev/null and b/.yarn/cache/caniuse-lite-npm-1.0.30001435-7cebb35f0a-ec88b9c37f.zip differ diff --git a/.yarn/cache/highcharts-npm-10.3.1-e67a887ff6-8a1cf9a363.zip b/.yarn/cache/highcharts-npm-10.3.2-1672942f09-43cb42b24c.zip similarity index 63% rename from .yarn/cache/highcharts-npm-10.3.1-e67a887ff6-8a1cf9a363.zip rename to .yarn/cache/highcharts-npm-10.3.2-1672942f09-43cb42b24c.zip index 4713b4599..410fae01e 100644 Binary files a/.yarn/cache/highcharts-npm-10.3.1-e67a887ff6-8a1cf9a363.zip and b/.yarn/cache/highcharts-npm-10.3.2-1672942f09-43cb42b24c.zip differ diff --git a/.yarn/cache/isbinaryfile-npm-4.0.10-91d1251522-a6b28db7e2.zip b/.yarn/cache/isbinaryfile-npm-4.0.10-91d1251522-a6b28db7e2.zip new file mode 100644 index 000000000..33eb2b844 Binary files /dev/null and b/.yarn/cache/isbinaryfile-npm-4.0.10-91d1251522-a6b28db7e2.zip differ diff --git a/.yarn/cache/lightningcss-darwin-arm64-npm-1.16.1-0a412810bd-8.zip b/.yarn/cache/lightningcss-darwin-arm64-npm-1.16.1-0a412810bd-8.zip deleted file mode 100644 index a6eff1fae..000000000 Binary files a/.yarn/cache/lightningcss-darwin-arm64-npm-1.16.1-0a412810bd-8.zip and /dev/null differ diff --git a/.yarn/cache/lightningcss-darwin-arm64-npm-1.17.1-a84f0d052c-8.zip b/.yarn/cache/lightningcss-darwin-arm64-npm-1.17.1-a84f0d052c-8.zip new file mode 100644 index 000000000..dfd4d7360 Binary files /dev/null and b/.yarn/cache/lightningcss-darwin-arm64-npm-1.17.1-a84f0d052c-8.zip differ diff --git a/.yarn/cache/lightningcss-darwin-x64-npm-1.16.1-3f7b1b3519-8.zip b/.yarn/cache/lightningcss-darwin-x64-npm-1.16.1-3f7b1b3519-8.zip deleted file mode 100644 index 917bb90b5..000000000 Binary files a/.yarn/cache/lightningcss-darwin-x64-npm-1.16.1-3f7b1b3519-8.zip and /dev/null differ diff --git a/.yarn/cache/lightningcss-darwin-x64-npm-1.17.1-131957b733-8.zip b/.yarn/cache/lightningcss-darwin-x64-npm-1.17.1-131957b733-8.zip new file mode 100644 index 000000000..b91f791ac Binary files /dev/null and b/.yarn/cache/lightningcss-darwin-x64-npm-1.17.1-131957b733-8.zip differ diff --git a/.yarn/cache/lightningcss-linux-arm64-gnu-npm-1.16.1-3ca4dc231b-8.zip b/.yarn/cache/lightningcss-linux-arm64-gnu-npm-1.16.1-3ca4dc231b-8.zip deleted file mode 100644 index 52560346b..000000000 Binary files a/.yarn/cache/lightningcss-linux-arm64-gnu-npm-1.16.1-3ca4dc231b-8.zip and /dev/null differ diff --git a/.yarn/cache/lightningcss-linux-arm64-gnu-npm-1.17.1-5b0e0aecb4-8.zip b/.yarn/cache/lightningcss-linux-arm64-gnu-npm-1.17.1-5b0e0aecb4-8.zip new file mode 100644 index 000000000..d19b0a574 Binary files /dev/null and b/.yarn/cache/lightningcss-linux-arm64-gnu-npm-1.17.1-5b0e0aecb4-8.zip differ diff --git a/.yarn/cache/lightningcss-linux-x64-gnu-npm-1.16.1-04529113fe-8.zip b/.yarn/cache/lightningcss-linux-x64-gnu-npm-1.16.1-04529113fe-8.zip deleted file mode 100644 index f1e534241..000000000 Binary files a/.yarn/cache/lightningcss-linux-x64-gnu-npm-1.16.1-04529113fe-8.zip and /dev/null differ diff --git a/.yarn/cache/lightningcss-linux-x64-gnu-npm-1.17.1-39d6988913-8.zip b/.yarn/cache/lightningcss-linux-x64-gnu-npm-1.17.1-39d6988913-8.zip new file mode 100644 index 000000000..b51790367 Binary files /dev/null and b/.yarn/cache/lightningcss-linux-x64-gnu-npm-1.17.1-39d6988913-8.zip differ diff --git a/.yarn/cache/lightningcss-npm-1.16.1-dc51de6ab1-78ec1fa158.zip b/.yarn/cache/lightningcss-npm-1.17.1-7428f2d516-0bf9d5c932.zip similarity index 89% rename from .yarn/cache/lightningcss-npm-1.16.1-dc51de6ab1-78ec1fa158.zip rename to .yarn/cache/lightningcss-npm-1.17.1-7428f2d516-0bf9d5c932.zip index 05b035032..0c9e0adc5 100644 Binary files a/.yarn/cache/lightningcss-npm-1.16.1-dc51de6ab1-78ec1fa158.zip and b/.yarn/cache/lightningcss-npm-1.17.1-7428f2d516-0bf9d5c932.zip differ diff --git a/.yarn/cache/lightningcss-win32-x64-msvc-npm-1.16.1-40af4d14b2-8.zip b/.yarn/cache/lightningcss-win32-x64-msvc-npm-1.16.1-40af4d14b2-8.zip deleted file mode 100644 index 7f26c93ed..000000000 Binary files a/.yarn/cache/lightningcss-win32-x64-msvc-npm-1.16.1-40af4d14b2-8.zip and /dev/null differ diff --git a/.yarn/cache/lightningcss-win32-x64-msvc-npm-1.17.1-849d8d151b-8.zip b/.yarn/cache/lightningcss-win32-x64-msvc-npm-1.17.1-849d8d151b-8.zip new file mode 100644 index 000000000..4a9444797 Binary files /dev/null and b/.yarn/cache/lightningcss-win32-x64-msvc-npm-1.17.1-849d8d151b-8.zip differ diff --git a/.yarn/cache/luxon-npm-3.1.0-16e2508500-f8a850b759.zip b/.yarn/cache/luxon-npm-3.1.0-16e2508500-f8a850b759.zip deleted file mode 100644 index 6a145dd08..000000000 Binary files a/.yarn/cache/luxon-npm-3.1.0-16e2508500-f8a850b759.zip and /dev/null differ diff --git a/.yarn/cache/luxon-npm-3.1.1-64fe977c1d-388fb35d3c.zip b/.yarn/cache/luxon-npm-3.1.1-64fe977c1d-388fb35d3c.zip new file mode 100644 index 000000000..1e0c7262f Binary files /dev/null and b/.yarn/cache/luxon-npm-3.1.1-64fe977c1d-388fb35d3c.zip differ diff --git a/.yarn/cache/mime-npm-2.6.0-88b89d8de0-1497ba7b9f.zip b/.yarn/cache/mime-npm-2.6.0-88b89d8de0-1497ba7b9f.zip new file mode 100644 index 000000000..644ef2b53 Binary files /dev/null and b/.yarn/cache/mime-npm-2.6.0-88b89d8de0-1497ba7b9f.zip differ diff --git a/.yarn/cache/pinia-npm-2.0.26-0d96417fac-0d38cc0efc.zip b/.yarn/cache/pinia-npm-2.0.26-0d96417fac-0d38cc0efc.zip deleted file mode 100644 index c7cad55cd..000000000 Binary files a/.yarn/cache/pinia-npm-2.0.26-0d96417fac-0d38cc0efc.zip and /dev/null differ diff --git a/.yarn/cache/pinia-npm-2.0.27-3e0154e702-29c862ea43.zip b/.yarn/cache/pinia-npm-2.0.27-3e0154e702-29c862ea43.zip new file mode 100644 index 000000000..c0477d355 Binary files /dev/null and b/.yarn/cache/pinia-npm-2.0.27-3e0154e702-29c862ea43.zip differ diff --git a/dev/deploy-to-container/cli.js b/dev/deploy-to-container/cli.js index ae3d28de8..395f94246 100644 --- a/dev/deploy-to-container/cli.js +++ b/dev/deploy-to-container/cli.js @@ -235,6 +235,12 @@ async function main () { `VIRTUAL_HOST=${hostname}`, `VIRTUAL_PORT=8000` ], + Labels: { + appversion: `${argv.appversion}` ?? '0.0.0', + commit: `${argv.commit}` ?? 'unknown', + ghrunid: `${argv.ghrunid}` ?? '0', + hostname + }, HostConfig: { Binds: [ 'dt-assets:/assets' diff --git a/dev/shared-assets-sync/Dockerfile b/dev/shared-assets-sync/Dockerfile new file mode 100644 index 000000000..46541a77a --- /dev/null +++ b/dev/shared-assets-sync/Dockerfile @@ -0,0 +1,9 @@ +FROM alpine:3 + +RUN apk add --no-cache wget rsync +COPY docker/scripts/app-rsync-extras.sh /workspace/app-rsync-extras.sh +RUN chmod +x /workspace/app-rsync-extras.sh + +WORKDIR /workspace + +CMD ["sh", "app-rsync-extras.sh"] diff --git a/dev/tests/debug.sh b/dev/tests/debug.sh new file mode 100644 index 000000000..37e7bc3ab --- /dev/null +++ b/dev/tests/debug.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# This script recreate the same environment used during tests on GitHub Actions +# and drops you into a terminal at the point where the actual tests would be run. +# +# Refer to https://github.com/ietf-tools/datatracker/blob/main/.github/workflows/build.yml#L141-L155 +# for the commands to run next. +# +# Simply type "exit" + ENTER to exit and shutdown this test environment. + +echo "Fetching latest images..." +docker pull ghcr.io/ietf-tools/datatracker-app-base:latest +docker pull ghcr.io/ietf-tools/datatracker-db:latest +echo "Starting containers..." +docker compose -f docker-compose.debug.yml -p dtdebug up -d +echo "Copying working directory into container..." +docker compose -p dtdebug cp ../../. app:/__w/datatracker/datatracker/ +echo "Run prepare script..." +docker compose -p dtdebug exec app chmod +x ./dev/tests/prepare.sh +docker compose -p dtdebug exec app sh ./dev/tests/prepare.sh +docker compose -p dtdebug exec app /usr/local/bin/wait-for db:3306 -- echo "DB ready" +echo "=================================================================" +echo "Launching zsh terminal:" +docker compose -p dtdebug exec app /bin/zsh +echo "Shutting down containers..." +docker compose -p dtdebug down -v diff --git a/dev/tests/docker-compose.debug.yml b/dev/tests/docker-compose.debug.yml new file mode 100644 index 000000000..44649c117 --- /dev/null +++ b/dev/tests/docker-compose.debug.yml @@ -0,0 +1,33 @@ +# This docker-compose replicates the test workflow happening on GitHub during a PR / build check. +# To be used from the debug.sh script. + +version: '3.8' + +services: + app: + image: ghcr.io/ietf-tools/datatracker-app-base:latest + command: -f /dev/null + working_dir: /__w/datatracker/datatracker + entrypoint: tail + hostname: app + volumes: + - /var/run/docker.sock:/var/run/docker.sock + environment: + CI: 'true' + GITHUB_ACTIONS: 'true' + HOME: /github/home + db: + image: ghcr.io/ietf-tools/datatracker-db:latest + restart: unless-stopped + volumes: + - mariadb-data:/var/lib/mysql + environment: + MYSQL_ROOT_PASSWORD: RkTkDPFnKpko + MYSQL_DATABASE: ietf_utf8 + MYSQL_USER: django + MYSQL_PASSWORD: RkTkDPFnKpko + CI: 'true' + GITHUB_ACTIONS: 'true' + +volumes: + mariadb-data: diff --git a/docker/configs/settings_local.py b/docker/configs/settings_local.py index 2b3d54138..afcb5b202 100644 --- a/docker/configs/settings_local.py +++ b/docker/configs/settings_local.py @@ -54,7 +54,7 @@ SUBMIT_YANG_RFC_MODEL_DIR = '/assets/ietf-ftp/yang/rfcmod/' # Set INTERNAL_IPS for use within Docker. See https://knasmueller.net/fix-djangos-debug-toolbar-not-showing-inside-docker import socket hostname, _, ips = socket.gethostbyname_ex(socket.gethostname()) -INTERNAL_IPS = [".".join(ip.split(".")[:-1] + ["1"]) for ip in ips] +INTERNAL_IPS = [".".join(ip.split(".")[:-1] + ["1"]) for ip in ips] + ['127.0.0.1'] # DEV_TEMPLATE_CONTEXT_PROCESSORS = [ # 'ietf.context_processors.sql_debug', diff --git a/docker/scripts/app-rsync-extras.sh b/docker/scripts/app-rsync-extras.sh index b99082b53..660670d20 100755 --- a/docker/scripts/app-rsync-extras.sh +++ b/docker/scripts/app-rsync-extras.sh @@ -42,7 +42,6 @@ cat << EOF > "$EXCLUDE" *.diff *.doc *.exe -*.html *.mib *.new *.p7s diff --git a/ietf/api/views.py b/ietf/api/views.py index a6a51f667..e5fc3bac5 100644 --- a/ietf/api/views.py +++ b/ietf/api/views.py @@ -80,7 +80,7 @@ class PersonalInformationExportView(DetailView, JsonExportMixin): person = get_object_or_404(self.model, user=request.user) expand = ['searchrule', 'documentauthor', 'ad_document_set', 'ad_dochistory_set', 'docevent', 'ballotpositiondocevent', 'deletedevent', 'email_set', 'groupevent', 'role', 'rolehistory', 'iprdisclosurebase', - 'iprevent', 'liaisonstatementevent', 'whitelisted', 'schedule', 'constraint', 'schedulingevent', 'message', + 'iprevent', 'liaisonstatementevent', 'allowlisted', 'schedule', 'constraint', 'schedulingevent', 'message', 'sendqueue', 'nominee', 'topicfeedbacklastseen', 'alias', 'email', 'apikeys', 'personevent', 'reviewersettings', 'reviewsecretarysettings', 'unavailableperiod', 'reviewwish', 'nextreviewerinteam', 'reviewrequest', 'meetingregistration', 'submissionevent', 'preapproval', diff --git a/ietf/dbtemplate/fixtures/nomcom_templates.xml b/ietf/dbtemplate/fixtures/nomcom_templates.xml index dbaa7df3e..abf0cb58f 100644 --- a/ietf/dbtemplate/fixtures/nomcom_templates.xml +++ b/ietf/dbtemplate/fixtures/nomcom_templates.xml @@ -166,7 +166,7 @@ Thank you, Thank you for accepting your nomination for the position of $position. Please remember to complete and return the questionnaire for this position at your earliest opportunity. -The questionaire is repeated below for your convenience. +The questionnaire is repeated below for your convenience. -------- diff --git a/ietf/dbtemplate/templates/dbtemplate/template_edit.html b/ietf/dbtemplate/templates/dbtemplate/template_edit.html index f27ab9c83..d4f0c755f 100644 --- a/ietf/dbtemplate/templates/dbtemplate/template_edit.html +++ b/ietf/dbtemplate/templates/dbtemplate/template_edit.html @@ -15,7 +15,7 @@
{{ template.type.name }} {% if template.type.slug == "rst" %}

This template uses the syntax of reStructuredText. Get a quick reference at http://docutils.sourceforge.net/docs/user/rst/quickref.html.

-

You can do variable interpolation with $varialbe if the template allows any variable.

+

You can do variable interpolation with $variable if the template allows any variable.

{% endif %} {% if template.type.slug == "django" %}

This template uses the syntax of the default django template framework. Get more info at https://docs.djangoproject.com/en/dev/topics/templates/.

@@ -43,4 +43,4 @@ -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/ietf/dbtemplate/templates/dbtemplate/template_show.html b/ietf/dbtemplate/templates/dbtemplate/template_show.html index a3a77ed45..527c9dedf 100644 --- a/ietf/dbtemplate/templates/dbtemplate/template_show.html +++ b/ietf/dbtemplate/templates/dbtemplate/template_show.html @@ -15,7 +15,7 @@
{{ template.type.name }} {% if template.type.slug == "rst" %}

This template uses the syntax of reStructuredText. Get a quick reference at http://docutils.sourceforge.net/docs/user/rst/quickref.html.

-

You can do variable interpolation with $varialbe if the template allows any variable.

+

You can do variable interpolation with $variable if the template allows any variable.

{% endif %} {% if template.type.slug == "django" %}

This template uses the syntax of the default django template framework. Get more info at https://docs.djangoproject.com/en/dev/topics/templates/.

@@ -37,4 +37,4 @@
{{ template.content|escape }}
-{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/ietf/doc/forms.py b/ietf/doc/forms.py index 372c43ab0..53a8d3b07 100644 --- a/ietf/doc/forms.py +++ b/ietf/doc/forms.py @@ -75,7 +75,13 @@ class AdForm(forms.Form): self.fields['ad'].choices = list(choices) + [("", "-------"), (ad_pk, Person.objects.get(pk=ad_pk).plain_name())] class NotifyForm(forms.Form): - notify = forms.CharField(max_length=255, help_text="List of email addresses to receive state notifications, separated by comma.", label="Notification list", required=False) + notify = forms.CharField( + widget=forms.Textarea, + max_length=1023, + help_text="List of email addresses to receive state notifications, separated by comma.", + label="Notification list", + required=False, + ) def clean_notify(self): addrspecs = [x.strip() for x in self.cleaned_data["notify"].split(',')] @@ -223,4 +229,4 @@ class ExtResourceForm(forms.Form): @staticmethod def valid_resource_tags(): - return ExtResourceName.objects.all().order_by('slug').values_list('slug', flat=True) \ No newline at end of file + return ExtResourceName.objects.all().order_by('slug').values_list('slug', flat=True) diff --git a/ietf/doc/migrations/0048_send_notices.py b/ietf/doc/migrations/0048_send_notices.py new file mode 100644 index 000000000..f2c6959d8 --- /dev/null +++ b/ietf/doc/migrations/0048_send_notices.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.28 on 2022-12-05 17:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('doc', '0047_tzaware_deletedevents'), + ] + + operations = [ + migrations.AlterField( + model_name='dochistory', + name='notify', + field=models.TextField(blank=True, max_length=1023), + ), + migrations.AlterField( + model_name='document', + name='notify', + field=models.TextField(blank=True, max_length=1023), + ), + ] diff --git a/ietf/doc/models.py b/ietf/doc/models.py index bb2175355..670ed9139 100644 --- a/ietf/doc/models.py +++ b/ietf/doc/models.py @@ -8,6 +8,8 @@ import io import os import rfc2html +from pathlib import Path +from lxml import etree from typing import Optional, TYPE_CHECKING from weasyprint import HTML as wpHTML @@ -21,6 +23,7 @@ from django.conf import settings from django.utils import timezone from django.utils.encoding import force_text from django.utils.html import mark_safe # type:ignore +from django.contrib.staticfiles import finders import debug # pyflakes:ignore @@ -108,7 +111,7 @@ class DocumentInfo(models.Model): ad = ForeignKey(Person, verbose_name="area director", related_name='ad_%(class)s_set', blank=True, null=True) shepherd = ForeignKey(Email, related_name='shepherd_%(class)s_set', blank=True, null=True) expires = models.DateTimeField(blank=True, null=True) - notify = models.CharField(max_length=255, blank=True) + notify = models.TextField(max_length=1023, blank=True) external_url = models.URLField(blank=True) uploaded_filename = models.TextField(blank=True) note = models.TextField(blank=True) @@ -541,6 +544,49 @@ class DocumentInfo(models.Model): def text_or_error(self): return self.text() or "Error; cannot read '%s'"%self.get_base_name() + def html_body(self, classes=""): + if self.get_state_slug() == "rfc": + try: + html = Path( + os.path.join(settings.RFC_PATH, self.canonical_name() + ".html") + ).read_text() + except (IOError, UnicodeDecodeError): + return None + else: + try: + html = Path( + os.path.join( + settings.INTERNET_ALL_DRAFTS_ARCHIVE_DIR, + self.name + "-" + self.rev + ".html", + ) + ).read_text() + except (IOError, UnicodeDecodeError): + return None + + # If HTML was generated by rfc2html, do not return it. Caller + # will use htmlize() to use a more current rfc2html to + # generate an HTMLized version. TODO: There should be a + # better way to determine how an HTML format was generated. + if html.startswith("
"):
+            return None
+
+        # get body
+        etree_html = etree.HTML(html)
+        if etree_html is None:
+            return None
+        body = etree_html.xpath("//body")[0]
+        body.tag = "div"
+        if classes:
+            body.attrib["class"] = classes
+
+        # remove things
+        for tag in ["script"]:
+            for t in body.xpath(f"//{tag}"):
+                t.getparent().remove(t)
+        html = etree.tostring(body, encoding=str, method="html")
+
+        return html
+
     def htmlized(self):
         name = self.get_base_name()
         text = self.text()
@@ -566,17 +612,29 @@ class DocumentInfo(models.Model):
 
     def pdfized(self):
         name = self.get_base_name()
-        text = self.text()
-        cache = caches['pdfized']
-        cache_key = name.split('.')[0]
+        text = self.html_body(classes="rfchtml")
+        stylesheets = [finders.find("ietf/css/document_html_referenced.css")]
+        if text:
+            stylesheets.append(finders.find("ietf/css/document_html_txt.css"))
+        else:
+            text = self.htmlized()
+            stylesheets.append(io.BytesIO(b"body { font-size: 9.2pt; }"))
+
+        cache = caches["pdfized"]
+        cache_key = name.split(".")[0]
         try:
             pdf = cache.get(cache_key)
         except EOFError:
             pdf = None
         if not pdf:
-            html = rfc2html.markup(text, path=settings.PDFIZER_URL_PREFIX)
             try:
-                pdf = wpHTML(string=html.replace('\xad','')).write_pdf(stylesheets=[io.BytesIO(b'html { font-size: 94%;}')])
+                pdf = wpHTML(
+                    string=text, base_url=settings.IDTRACKER_BASE_URL
+                ).write_pdf(
+                    stylesheets=stylesheets,
+                    presentational_hints=True,
+                    optimize_size=("fonts", "images"),
+                )
             except AssertionError:
                 pdf = None
             if pdf:
diff --git a/ietf/doc/templatetags/ietf_filters.py b/ietf/doc/templatetags/ietf_filters.py
index e5353d1de..1137cf636 100644
--- a/ietf/doc/templatetags/ietf_filters.py
+++ b/ietf/doc/templatetags/ietf_filters.py
@@ -260,8 +260,8 @@ def urlize_ietf_docs(string, autoescape=None):
 
 urlize_ietf_docs = stringfilter(urlize_ietf_docs)
 
-@register.filter(name='urlize_related_source_list', is_safe=True, needs_autoescape=True)
-def urlize_related_source_list(related, autoescape=None):
+@register.filter(name='urlize_related_source_list', is_safe=True, document_html=False)
+def urlize_related_source_list(related, document_html=False):
     """Convert a list of RelatedDocuments into list of links using the source document's canonical name"""
     links = []
     names = set()
@@ -273,10 +273,9 @@ def urlize_related_source_list(related, autoescape=None):
             continue
         names.add(name)
         titles.add(title)
-        url = urlreverse('ietf.doc.views_doc.document_main', kwargs=dict(name=name))
-        if autoescape:
-            name = escape(name)
-            title = escape(title)
+        url = urlreverse('ietf.doc.views_doc.document_main' if document_html is False else 'ietf.doc.views_doc.document_html', kwargs=dict(name=name))
+        name = escape(name)
+        title = escape(title)
         links.append(mark_safe(
             '%(name)s' % dict(name=prettify_std_name(name),
                                                                       title=title,
@@ -284,17 +283,16 @@ def urlize_related_source_list(related, autoescape=None):
         ))
     return links
         
-@register.filter(name='urlize_related_target_list', is_safe=True, needs_autoescape=True)
-def urlize_related_target_list(related, autoescape=None):
+@register.filter(name='urlize_related_target_list', is_safe=True, document_html=False)
+def urlize_related_target_list(related, document_html=False):
     """Convert a list of RelatedDocuments into list of links using the target document's canonical name"""
     links = []
     for rel in related:
         name=rel.target.document.canonical_name()
         title = rel.target.document.title
-        url = urlreverse('ietf.doc.views_doc.document_main', kwargs=dict(name=name))
-        if autoescape:
-            name = escape(name)
-            title = escape(title)
+        url = urlreverse('ietf.doc.views_doc.document_main' if document_html is False else 'ietf.doc.views_doc.document_html', kwargs=dict(name=name))
+        name = escape(name)
+        title = escape(title)
         links.append(mark_safe(
             '%(name)s' % dict(name=prettify_std_name(name),
                                                                       title=title,
@@ -554,6 +552,19 @@ def consensus(doc):
     else:
         return "Unknown"
 
+
+@register.filter
+def std_level_to_label_format(doc):
+    """Returns valid Bootstrap classes to label a status level badge."""
+    if doc.is_rfc():
+        if doc.related_that("obs"):
+            return "obs"
+        else:
+            return doc.std_level_id
+    else:
+        return "draft"
+
+
 @register.filter
 def pos_to_label_format(text):
     """Returns valid Bootstrap classes to label a ballot position."""
diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py
index 533c61fd5..36431cb02 100644
--- a/ietf/doc/tests.py
+++ b/ietf/doc/tests.py
@@ -489,7 +489,7 @@ Table of Contents
 1.  Introduction
 
    This document describes how to make the Martian networks work.  The
-   methods used in Earth do not directly translate to the efficent
+   methods used in Earth do not directly translate to the efficient
    networks on Mars, as the topographical differences caused by planets.
    For example the avian carriers, cannot be used in the Mars, thus
    RFC1149 ([RFC1149]) cannot be used in Mars.
@@ -730,13 +730,13 @@ Man                    Expires September 22, 2015               [Page 3]
 
         r = self.client.get(urlreverse("ietf.doc.views_doc.document_html", kwargs=dict(name=draft.name)))
         self.assertEqual(r.status_code, 200)
-        self.assertContains(r, "Versions:")
+        self.assertContains(r, "Select version")
         self.assertContains(r, "Deimos street")
         q = PyQuery(r.content)
         self.assertEqual(q('title').text(), 'draft-ietf-mars-test-01')
-        self.assertEqual(len(q('.rfcmarkup pre')), 4)
-        self.assertEqual(len(q('.rfcmarkup span.h1')), 2)
-        self.assertEqual(len(q('.rfcmarkup a[href]')), 41)
+        self.assertEqual(len(q('.rfcmarkup pre')), 3)
+        self.assertEqual(len(q('.rfcmarkup span.h1, .rfcmarkup h1')), 2)
+        self.assertEqual(len(q('.rfcmarkup a[href]')), 28)
 
         r = self.client.get(urlreverse("ietf.doc.views_doc.document_html", kwargs=dict(name=draft.name, rev=draft.rev)))
         self.assertEqual(r.status_code, 200)
diff --git a/ietf/doc/tests_charter.py b/ietf/doc/tests_charter.py
index c420fdd0a..f65cf14e0 100644
--- a/ietf/doc/tests_charter.py
+++ b/ietf/doc/tests_charter.py
@@ -448,8 +448,8 @@ class EditCharterTests(TestCase):
         # Regenerate does not save!
         self.assertEqual(charter.notify,newlist)
         q = PyQuery(r.content)
-        formlist = q('form input[name=notify]')[0].value
-        self.assertEqual(formlist, None)
+        formlist = q('form textarea[name=notify]')[0].value.strip()
+        self.assertEqual(formlist, "")
 
     def test_edit_ad(self):
 
@@ -840,4 +840,4 @@ class EditCharterTests(TestCase):
         ad_role = RoleFactory(group__type_id='area',name_id='ad')
         charter = CharterFactory(group__type_id='wg',group__state_id='bof',group__parent=ad_role.group)
         e1,_ = default_review_text(charter.group, charter, Person.objects.get(name="(System)"))
-        self.assertTrue('A new IETF WG has been proposed' in e1.text)
\ No newline at end of file
+        self.assertTrue('A new IETF WG has been proposed' in e1.text)
diff --git a/ietf/doc/tests_conflict_review.py b/ietf/doc/tests_conflict_review.py
index 4cc501ec0..6fa1a4d9b 100644
--- a/ietf/doc/tests_conflict_review.py
+++ b/ietf/doc/tests_conflict_review.py
@@ -105,7 +105,7 @@ class ConflictReviewTests(TestCase):
         r = self.client.get(url)
         self.assertEqual(r.status_code, 200)
         q = PyQuery(r.content)
-        self.assertEqual(len(q('form input[name=notify]')),1)
+        self.assertEqual(len(q('form textarea[name=notify]')), 1)
         self.assertEqual(len(q('form select[name=ad]')),0)
 
         # successfully starts a review, and notifies the secretariat
@@ -179,8 +179,8 @@ class ConflictReviewTests(TestCase):
         r = self.client.get(url)
         self.assertEqual(r.status_code, 200)
         q = PyQuery(r.content)
-        self.assertEqual(len(q('form input[name=notify]')),1)
-        self.assertEqual(doc.notify,q('form input[name=notify]')[0].value)
+        self.assertEqual(len(q('form textarea[name=notify]')), 1)
+        self.assertEqual(doc.notify, q('form textarea[name=notify]')[0].value.strip())
 
         # change notice list
         newlist = '"Foo Bar" '
@@ -197,7 +197,7 @@ class ConflictReviewTests(TestCase):
         # Regenerate does not save!
         self.assertEqual(doc.notify,newlist)
         q = PyQuery(r.content)
-        self.assertEqual(None,q('form input[name=notify]')[0].value)
+        self.assertEqual("", q('form textarea[name=notify]')[0].value.strip())
 
     def test_edit_ad(self):
         doc = Document.objects.get(name='conflict-review-imaginary-irtf-submission')
diff --git a/ietf/doc/tests_draft.py b/ietf/doc/tests_draft.py
index a2522b270..c038b27b2 100644
--- a/ietf/doc/tests_draft.py
+++ b/ietf/doc/tests_draft.py
@@ -456,7 +456,7 @@ class EditInfoTests(TestCase):
         self.assertEqual(r.status_code, 200)
         q = PyQuery(r.content)
         self.assertEqual(len(q('form select[name=intended_std_level]')), 1)
-        self.assertEqual(None,q('form input[name=notify]')[0].value)
+        self.assertEqual("", q('form textarea[name=notify]')[0].value.strip())
 
         # add
         events_before = draft.docevent_set.count()
@@ -947,7 +947,7 @@ class IndividualInfoFormsTests(TestCase):
         r = self.client.get(url)
         self.assertEqual(r.status_code,200)
         q = PyQuery(r.content)
-        self.assertEqual(len(q('form input[name=notify]')),1)
+        self.assertEqual(len(q('form textarea[name=notify]')), 1)
 
         # Provide a list
         r = self.client.post(url,dict(notify="TJ2APh2P@ietf.org",save_addresses="1"))
@@ -962,7 +962,7 @@ class IndividualInfoFormsTests(TestCase):
         # Regenerate does not save!
         self.assertEqual(doc.notify,'TJ2APh2P@ietf.org')
         q = PyQuery(r.content)
-        self.assertEqual(None,q('form input[name=notify]')[0].value)
+        self.assertEqual("", q('form textarea[name=notify]')[0].value.strip())
 
     def test_doc_change_intended_status(self):
         url = urlreverse('ietf.doc.views_draft.change_intention', kwargs=dict(name=self.docname))
diff --git a/ietf/doc/tests_status_change.py b/ietf/doc/tests_status_change.py
index cd8f859dd..571d9ed1d 100644
--- a/ietf/doc/tests_status_change.py
+++ b/ietf/doc/tests_status_change.py
@@ -147,8 +147,8 @@ class StatusChangeTests(TestCase):
         r = self.client.get(url)
         self.assertEqual(r.status_code, 200)
         q = PyQuery(r.content)
-        self.assertEqual(len(q('form input[name=notify]')),1)
-        self.assertEqual(doc.notify,q('form input[name=notify]')[0].value)
+        self.assertEqual(len(q('form textarea[name=notify]')), 1)
+        self.assertEqual(doc.notify, q('form textarea[name=notify]')[0].value.strip())
 
         # change notice list
         newlist = '"Foo Bar" '
@@ -169,8 +169,8 @@ class StatusChangeTests(TestCase):
         # Regenerate does not save!
         self.assertEqual(doc.notify,newlist)
         q = PyQuery(r.content)
-        formlist = q('form input[name=notify]')[0].value
-        self.assertEqual(None,formlist)
+        formlist = q('form textarea[name=notify]')[0].value.strip()
+        self.assertEqual("", formlist)
 
     def test_edit_title(self):
         doc = Document.objects.get(name='status-change-imaginary-mid-review')
diff --git a/ietf/doc/utils.py b/ietf/doc/utils.py
index 27d36284a..20e112037 100644
--- a/ietf/doc/utils.py
+++ b/ietf/doc/utils.py
@@ -995,41 +995,6 @@ def get_search_cache_key(params):
     key = "doc:document:search:" + hashlib.sha512(json.dumps(kwargs, sort_keys=True).encode('utf-8')).hexdigest()
     return key
     
-def label_wrap(label, items, joiner=',', max=50):
-    lines = []
-    if not items:
-        return lines
-    line = '%s: %s' % (label, items[0])
-    for item in items[1:]:
-        if len(line)+len(joiner+' ')+len(item) > max:
-            lines.append(line+joiner)
-            line = ' '*(len(label)+len(': ')) + item
-        else:
-            line += joiner+' '+item
-    if line:
-        lines.append(line)
-    return lines
-
-def join_justified(left, right, width=72):
-    count = max(len(left), len(right))
-    left = left + ['']*(count-len(left))
-    right = right + ['']*(count-len(right))
-    lines = []
-    i = 0
-    while True:
-        l = left[i]
-        r = right[i]
-        if len(l)+1+len(r) > width:
-            left = left + ['']
-            right = right[:i] + [''] + right[i:]
-            r = right[i]
-            count += 1
-        lines.append( l + ' ' + r.rjust(width-len(l)-1) )
-        i += 1
-        if i >= count:
-            break
-    return lines
-
 def build_file_urls(doc):
     if isinstance(doc,Document) and doc.get_state_slug() == "rfc":
         name = doc.canonical_name()
@@ -1069,135 +1034,6 @@ def build_file_urls(doc):
 
     return file_urls, found_types
 
-def build_doc_supermeta_block(doc):
-    items = []
-    items.append(f'[Search]')
-
-    file_urls, found_types = build_file_urls(doc)
-    file_urls = [('txt',url) if label=='plain text' else (label,url) for label,url in file_urls]
-
-    if file_urls:
-        file_labels = {
-            'txt' : 'Plaintext version of this document',
-            'xml' : 'XML source for this document',
-            'pdf' : 'PDF version of this document',
-            'html' : 'HTML version of this document, from XML2RFC',
-            'bibtex' : 'BibTex entry for this document',
-        }
-        parts=[]
-        for label,url in file_urls:
-            if 'htmlized' not in label:
-                file_label=file_labels.get(label,'')
-                title_attribute = f' title="{file_label}"' if file_label else ''
-                partstring = f'{label}' 
-                parts.append(partstring)
-        items.append('[' + '|'.join(parts) + ']')
-
-    items.append(f'[Tracker]')
-    if doc.group.acronym != 'none':
-        items.append(f'[WG]')
-    items.append(f'[Email]')
-    if doc.rev != "00":
-        items.append(f'[Diff1]')
-        items.append(f'[Diff2]')
-    items.append(f'[Nits]')
-
-    return ' '.join(items)
-
-def build_doc_meta_block(doc, path):
-    def add_markup(path, doc, lines):
-        is_hst = doc.is_dochistory()
-        rev = doc.rev
-        if is_hst:
-            doc = doc.doc
-        name = doc.name
-        rfcnum = doc.rfc_number()
-        errata_url = settings.RFC_EDITOR_ERRATA_URL.format(rfc_number=rfcnum) if not is_hst else ""
-        ipr_url = "%s?submit=draft&id=%s" % (urlreverse('ietf.ipr.views.search'), name)
-        for i, line in enumerate(lines):
-            # add draft links
-            line = re.sub(r'\b(draft-[-a-z0-9]+)\b', r'\g<1>'%(path, ), line)
-            # add rfcXXXX to RFC links
-            line = re.sub(r' (rfc[0-9]+)\b', r' \g<1>'%(path, ), line)
-            # add XXXX to RFC links
-            line = re.sub(r' ([0-9]{3,5})\b', r' \g<1>'%(path, ), line)
-            # add draft revision links
-            line = re.sub(r' ([0-9]{2})\b', r' \g<1>'%(path, name, ), line)
-            if rfcnum:
-                # add errata link
-                line = re.sub(r'Errata exist', r'Errata exist'%(errata_url, ), line)
-            if is_hst or not rfcnum:
-                # make current draft rev bold
-                line = re.sub(r'>(%s)<'%rev, r'>\g<1><', line)
-            line = re.sub(r'IPR declarations', r'IPR declarations'%(ipr_url, ), line)
-            line = line.replace(r'[txt]', r'[txt]' % doc.get_href())
-            lines[i] = line
-        return lines
-    #
-    now = timezone.now()
-    draft_state = doc.get_state('draft')
-    block = ''
-    meta = {}
-    if doc.type_id == 'draft':
-        revisions = []
-        ipr = doc.related_ipr()
-        if ipr:
-            meta['ipr'] = [ "IPR declarations" ]
-        if doc.is_rfc() and not doc.is_dochistory():
-            if not doc.name.startswith('rfc'):
-                meta['from'] = [ "%s-%s"%(doc.name, doc.rev) ]
-            meta['errata'] = [ "Errata exist" ] if doc.tags.filter(slug='errata').exists() else []
-            
-            meta['obsoletedby'] = [ document.rfc_number() for alias in doc.related_that('obs') for document in alias.docs.all() ]
-            meta['obsoletedby'].sort()
-            meta['updatedby'] = [ document.rfc_number() for alias in doc.related_that('updates') for document in alias.docs.all() ]
-            meta['updatedby'].sort()
-            meta['stdstatus'] = [ doc.std_level.name ]
-        else:
-            dd = doc.doc if doc.is_dochistory() else doc
-            revisions += [ '(%s)%s'%(d.name, ' '*(2-((len(d.name)-1)%3))) for d in dd.replaces() ]
-            revisions += doc.revisions()
-            if doc.is_dochistory() and doc.doc.is_rfc():
-                revisions += [ doc.doc.canonical_name() ]
-            else:
-                revisions += [ d.name for d in doc.replaced_by() ]
-            meta['versions'] = revisions
-            if not doc.is_dochistory and draft_state.slug == 'active' and now > doc.expires:
-                # Active past expiration date
-                meta['active'] = [ 'Document is active' ]
-                meta['state' ] = [ doc.friendly_state() ]
-            intended_std = doc.intended_std_level if doc.intended_std_level else None
-            if intended_std:
-                if intended_std.slug in ['ps', 'ds', 'std']:
-                    meta['stdstatus'] = [ "Standards Track" ]
-                else:
-                    meta['stdstatus'] = [ intended_std.name ]
-    elif doc.type_id == 'charter':
-        meta['versions'] = doc.revisions()
-    #
-    # Add markup to items that needs it.
-    if 'versions' in meta:
-        meta['versions'] = label_wrap('Versions', meta['versions'], joiner="")
-    for label in ['Obsoleted by', 'Updated by', 'From' ]:
-        item = label.replace(' ','').lower()
-        if item in meta and meta[item]:
-            meta[item] = label_wrap(label, meta[item])
-    #
-    left = []
-    right = []
-    #right = [ '[txt]']
-    for item in [ 'from', 'versions', 'obsoletedby', 'updatedby', ]:
-        if item in meta and meta[item]:
-            left += meta[item]
-    for item in ['stdstatus', 'active', 'state', 'ipr', 'errata', ]:
-        if item in meta and meta[item]:
-            right += meta[item]
-    lines = join_justified(left, right)
-    block = '\n'.join(add_markup(path, doc, lines))
-    #
-    return block
-
-
 def augment_docs_and_user_with_user_info(docs, user):
     """Add attribute to each document with whether the document is tracked
     or has a review wish by the user or not, and the review teams the user is on."""
@@ -1362,5 +1198,9 @@ def bibxml_for_draft(doc, rev=None):
     else:
         doc.date = doc.time.astimezone(tzinfo).date()      # Even if this may be incorrect, what would be better?
 
-    return render_to_string('doc/bibxml.xml', {'name':doc.name, 'doc': doc, 'doc_bibtype':'I-D'})
+    name = doc.name if isinstance(doc, Document) else doc.doc.name
+    if name.startswith('rfc'): # bibxml3 does not speak of RFCs
+        raise Http404()
+        
+    return render_to_string('doc/bibxml.xml', {'name':name, 'doc':doc, 'doc_bibtype':'I-D'})
 
diff --git a/ietf/doc/views_conflict_review.py b/ietf/doc/views_conflict_review.py
index e42c13872..5efd62dbe 100644
--- a/ietf/doc/views_conflict_review.py
+++ b/ietf/doc/views_conflict_review.py
@@ -370,13 +370,25 @@ def approve_conflict_review(request, name):
                                    ))
 
 class SimpleStartReviewForm(forms.Form):
-    notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas.", required=False)
+    notify = forms.CharField(
+        widget=forms.Textarea,
+        max_length=1023,
+        label="Notice emails",
+        help_text="Separate email addresses with commas.",
+        required=False,
+    )
 
 class StartReviewForm(forms.Form):
     ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active",role__group__type='area').order_by('name'), 
                                 label="Shepherding AD", empty_label="(None)", required=True)
     create_in_state = forms.ModelChoiceField(State.objects.filter(used=True, type="conflrev", slug__in=("needshep", "adrev")), empty_label=None, required=False)
-    notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas.", required=False)
+    notify = forms.CharField(
+        widget=forms.Textarea,
+        max_length=1023,
+        label="Notice emails",
+        help_text="Separate email addresses with commas.",
+        required=False,
+    )
     telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False, widget=forms.Select(attrs={'onchange':'make_bold()'}))
 
     def __init__(self, *args, **kwargs):
@@ -523,4 +535,4 @@ def start_review_as_stream_owner(request, name):
                               {'form':   form,
                                'doc_to_review': doc_to_review,
                               },
-                          )
\ No newline at end of file
+                          )
diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py
index bdf3b6c1a..4c7d21645 100644
--- a/ietf/doc/views_doc.py
+++ b/ietf/doc/views_doc.py
@@ -49,6 +49,7 @@ from django.template.loader import render_to_string
 from django.urls import reverse as urlreverse
 from django.conf import settings
 from django import forms
+from django.contrib.staticfiles import finders
 
 
 import debug                            # pyflakes:ignore
@@ -61,9 +62,9 @@ from ietf.doc.utils import (add_links_in_new_revision_events, augment_events_wit
     can_adopt_draft, can_unadopt_draft, get_chartering_type, get_tags_for_stream_id,
     needed_ballot_positions, nice_consensus, prettify_std_name, update_telechat, has_same_ballot,
     get_initial_notify, make_notify_changed_event, make_rev_history, default_consensus,
-    add_events_message_info, get_unicode_document_content, build_doc_meta_block,
+    add_events_message_info, get_unicode_document_content,
     augment_docs_and_user_with_user_info, irsg_needed_ballot_positions, add_action_holder_change_event,
-    build_doc_supermeta_block, build_file_urls, update_documentauthors, fuzzy_find_documents,
+    build_file_urls, update_documentauthors, fuzzy_find_documents,
     bibxml_for_draft)
 from ietf.doc.utils_bofreq import bofreq_editors, bofreq_responsible
 from ietf.group.models import Role, Group
@@ -140,12 +141,12 @@ def interesting_doc_relations(doc):
 
     return interesting_relations_that, interesting_relations_that_doc
 
-def document_main(request, name, rev=None):
+def document_main(request, name, rev=None, document_html=False):
     doc = get_object_or_404(Document.objects.select_related(), docalias__name=name)
 
     # take care of possible redirections
     aliases = DocAlias.objects.filter(docs=doc).values_list("name", flat=True)
-    if rev==None and doc.type_id == "draft" and not name.startswith("rfc"):
+    if document_html is False and rev==None and doc.type_id == "draft" and not name.startswith("rfc"):
         for a in aliases:
             if a.startswith("rfc"):
                 return redirect("ietf.doc.views_doc.document_main", name=a)
@@ -169,7 +170,7 @@ def document_main(request, name, rev=None):
                 doc = h
                 break
 
-        if not snapshot:
+        if not snapshot and document_html is False:
             return redirect('ietf.doc.views_doc.document_main', name=name)
 
         if doc.type_id == "charter":
@@ -184,7 +185,6 @@ def document_main(request, name, rev=None):
 
     top = render_document_top(request, doc, "status", name)
 
-
     telechat = doc.latest_event(TelechatDocEvent, type="scheduled_for_telechat")
     if telechat and (not telechat.telechat_date or telechat.telechat_date < date_today(settings.TIME_ZONE)):
        telechat = None
@@ -446,8 +446,22 @@ def document_main(request, name, rev=None):
         else:
             stream_desc = "(None)"
 
-        return render(request, "doc/document_draft.html",
+        html = None
+        js = None
+        css = None
+        if document_html:
+            html = doc.html_body()
+            if request.COOKIES.get("pagedeps") == "inline":
+                js = Path(finders.find("ietf/js/document_html.js")).read_text()
+                css = Path(finders.find("ietf/css/document_html_inline.css")).read_text()
+                if html:
+                    css += Path(finders.find("ietf/css/document_html_txt.css")).read_text()
+        return render(request, "doc/document_draft.html" if document_html is False else "doc/document_html.html",
                                   dict(doc=doc,
+                                       document_html=document_html,
+                                       css=css,
+                                       js=js,
+                                       html=html,
                                        group=group,
                                        top=top,
                                        name=name,
@@ -787,7 +801,6 @@ def document_raw_id(request, name, rev=None, ext=None):
     except:
         raise Http404
 
-
 def document_html(request, name, rev=None):
     found = fuzzy_find_documents(name, rev)
     num_found = found.documents.count()
@@ -811,30 +824,7 @@ def document_html(request, name, rev=None):
     if not os.path.exists(doc.get_file_name()):
         raise Http404("File not found: %s" % doc.get_file_name())
 
-    if doc.type_id in ['draft',]:
-        doc.supermeta = build_doc_supermeta_block(doc)
-        doc.meta = build_doc_meta_block(doc, settings.HTMLIZER_URL_PREFIX)
-
-    doccolor = 'bgwhite' # Unknown
-    if doc.type_id=='draft':
-        if doc.is_rfc():
-            if doc.related_that('obs'):
-                doccolor = 'bgbrown'
-            else:
-                doccolor = {
-                    'ps'   : 'bgblue',
-                    'exp'  : 'bgyellow',
-                    'inf'  : 'bgorange',
-                    'ds'   : 'bgcyan',
-                    'hist' : 'bggrey',
-                    'std'  : 'bggreen',
-                    'bcp'  : 'bgmagenta',
-                    'unkn' : 'bgwhite',
-                }.get(doc.std_level_id, 'bgwhite')
-        else:
-            doccolor = 'bgred' # Draft
-
-    return render(request, "doc/document_html.html", {"doc":doc, "doccolor":doccolor })
+    return document_main(request, name, rev=rev, document_html=True)
 
 def document_pdfized(request, name, rev=None, ext=None):
 
diff --git a/ietf/doc/views_draft.py b/ietf/doc/views_draft.py
index ee5eb9c86..63cdbe9ea 100644
--- a/ietf/doc/views_draft.py
+++ b/ietf/doc/views_draft.py
@@ -515,7 +515,13 @@ class EditInfoForm(forms.Form):
     area = forms.ModelChoiceField(Group.objects.filter(type="area", state="active"), empty_label="(None - individual submission)", required=False, label="Assigned to area")
     ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active",role__group__type='area').order_by('name'), label="Responsible AD", empty_label="(None)", required=True)
     create_in_state = forms.ModelChoiceField(State.objects.filter(used=True, type="draft-iesg", slug__in=("pub-req", "watching")), empty_label=None, required=False)
-    notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas.", required=False)
+    notify = forms.CharField(
+        widget=forms.Textarea,
+        max_length=1023,
+        label="Notice emails",
+        help_text="Separate email addresses with commas.",
+        required=False,
+    )
     note = forms.CharField(widget=forms.Textarea, label="IESG note", required=False, strip=False)
     telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False, widget=forms.Select(attrs={'onchange':'make_bold()'}))
     returning_item = forms.BooleanField(required=False)
diff --git a/ietf/doc/views_status_change.py b/ietf/doc/views_status_change.py
index d4868215a..99f82d435 100644
--- a/ietf/doc/views_status_change.py
+++ b/ietf/doc/views_status_change.py
@@ -468,7 +468,13 @@ class StartStatusChangeForm(forms.Form):
     ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active",role__group__type='area').order_by('name'), 
                                 label="Shepherding AD", empty_label="(None)", required=False)
     create_in_state = forms.ModelChoiceField(State.objects.filter(type="statchg", slug__in=("needshep", "adrev")), empty_label=None, required=False)
-    notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas.", required=False)
+    notify = forms.CharField(
+        widget=forms.Textarea,
+        max_length=1023,
+        label="Notice emails",
+        help_text="Separate email addresses with commas.",
+        required=False,
+    )
     telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False, widget=forms.Select(attrs={'onchange':'make_bold()'}))
     relations={}                        # type: Dict[str, str]
 
diff --git a/ietf/group/models.py b/ietf/group/models.py
index c4c1673ac..112522f2c 100644
--- a/ietf/group/models.py
+++ b/ietf/group/models.py
@@ -133,15 +133,15 @@ class Group(GroupInfo):
             role_names = [role_names]
         return user.is_authenticated and self.role_set.filter(name__in=role_names, person__user=user).exists()
 
-    def is_decendant_of(self, sought_parent):
+    def is_descendant_of(self, sought_parent):
         parent = self.parent
-        decendants = [ self, ]
-        while (parent != None) and (parent not in decendants):
-            decendants = [ parent ] + decendants
+        descendants = [ self, ]
+        while (parent != None) and (parent not in descendants):
+            descendants = [ parent ] + descendants
             if parent.acronym == sought_parent:
                 return True
             parent = parent.parent
-        log.assertion('parent not in decendants')
+        log.assertion('parent not in descendants')
         return False
 
     def get_chair(self):
diff --git a/ietf/group/tests_info.py b/ietf/group/tests_info.py
index 71763eae4..98af69ba0 100644
--- a/ietf/group/tests_info.py
+++ b/ietf/group/tests_info.py
@@ -1937,12 +1937,12 @@ class GroupParentLoopTests(TestCase):
         import signal
 
         def timeout_handler(signum, frame):
-            raise Exception("Infinite loop in parent links is not handeled properly.")
+            raise Exception("Infinite loop in parent links is not handled properly.")
 
         signal.signal(signal.SIGALRM, timeout_handler)
         signal.alarm(1)   # One second
         try:
-            test2.is_decendant_of("ietf")
+            test2.is_descendant_of("ietf")
         except AssertionError:
             pass
         except Exception:
diff --git a/ietf/ietfauth/forms.py b/ietf/ietfauth/forms.py
index 7a609c623..dab5ce374 100644
--- a/ietf/ietfauth/forms.py
+++ b/ietf/ietfauth/forms.py
@@ -18,7 +18,7 @@ from django_password_strength.widgets import PasswordStrengthInput, PasswordConf
 import debug                            # pyflakes:ignore
 
 from ietf.person.models import Person, Email
-from ietf.mailinglists.models import Whitelisted
+from ietf.mailinglists.models import Allowlisted
 from ietf.utils.text import isascii
 
 class RegistrationForm(forms.Form):
@@ -203,9 +203,9 @@ class ResetPasswordForm(forms.Form):
 class TestEmailForm(forms.Form):
     email = forms.EmailField(required=False)
 
-class WhitelistForm(forms.ModelForm):
+class AllowlistForm(forms.ModelForm):
     class Meta:
-        model = Whitelisted
+        model = Allowlisted
         exclude = ['by', 'time' ]
 
     
diff --git a/ietf/ietfauth/tests.py b/ietf/ietfauth/tests.py
index 8e4d14982..9fd75d06d 100644
--- a/ietf/ietfauth/tests.py
+++ b/ietf/ietfauth/tests.py
@@ -194,7 +194,7 @@ class IetfAuthTests(TestCase):
 
         self.assertTrue(self.username_in_htpasswd_file(email))
 
-    def test_create_whitelisted_account(self):
+    def test_create_allowlisted_account(self):
         email = "new-account@example.com"
 
         # add allowlist entry
@@ -202,13 +202,13 @@ class IetfAuthTests(TestCase):
         self.assertEqual(r.status_code, 302)
         self.assertEqual(urlsplit(r["Location"])[2], urlreverse(ietf.ietfauth.views.profile))
 
-        r = self.client.get(urlreverse(ietf.ietfauth.views.add_account_whitelist))
+        r = self.client.get(urlreverse(ietf.ietfauth.views.add_account_allowlist))
         self.assertEqual(r.status_code, 200)
-        self.assertContains(r, "Add a whitelist entry")
+        self.assertContains(r, "Add an allowlist entry")
 
-        r = self.client.post(urlreverse(ietf.ietfauth.views.add_account_whitelist), {"email": email})
+        r = self.client.post(urlreverse(ietf.ietfauth.views.add_account_allowlist), {"email": email})
         self.assertEqual(r.status_code, 200)
-        self.assertContains(r, "Whitelist entry creation successful")
+        self.assertContains(r, "Allowlist entry creation successful")
 
         # log out
         r = self.client.get(urlreverse('django.contrib.auth.views.logout'))
diff --git a/ietf/ietfauth/urls.py b/ietf/ietfauth/urls.py
index 12c749fbe..56daae053 100644
--- a/ietf/ietfauth/urls.py
+++ b/ietf/ietfauth/urls.py
@@ -24,5 +24,5 @@ urlpatterns = [
         url(r'^review/$', views.review_overview),
         url(r'^testemail/$', views.test_email),
         url(r'^username/$', views.change_username),
-        url(r'^whitelist/add/?$', views.add_account_whitelist),
+        url(r'^allowlist/add/?$', views.add_account_allowlist),
 ]
diff --git a/ietf/ietfauth/views.py b/ietf/ietfauth/views.py
index f9db86d76..f89bc0149 100644
--- a/ietf/ietfauth/views.py
+++ b/ietf/ietfauth/views.py
@@ -62,13 +62,11 @@ import debug                            # pyflakes:ignore
 
 from ietf.group.models import Role, Group
 from ietf.ietfauth.forms import ( RegistrationForm, PasswordForm, ResetPasswordForm, TestEmailForm,
-                                WhitelistForm, ChangePasswordForm, get_person_form, RoleEmailForm,
+                                AllowlistForm, ChangePasswordForm, get_person_form, RoleEmailForm,
                                 NewEmailForm, ChangeUsernameForm, PersonPasswordForm)
 from ietf.ietfauth.htpasswd import update_htpasswd_file
 from ietf.ietfauth.utils import role_required, has_role
-from ietf.mailinglists.models import Whitelisted
-# needed if we revert to higher barrier for account creation
-#from ietf.mailinglists.models import Subscribed, Whitelisted
+from ietf.mailinglists.models import Allowlisted
 from ietf.name.models import ExtResourceName
 from ietf.nomcom.models import NomCom
 from ietf.person.models import Person, Email, Alias, PersonalApiKey, PERSON_API_KEY_VALUES
@@ -128,7 +126,7 @@ def create_account(request):
 
             # The following is what to revert to should that lowered barrier prove problematic
             # existing = Subscribed.objects.filter(email=to_email).first()
-            # ok_to_create = ( Whitelisted.objects.filter(email=to_email).exists()
+            # ok_to_create = ( Allowlisted.objects.filter(email=to_email).exists()
             #     or existing and (existing.time + TimeDelta(seconds=settings.LIST_ACCOUNT_DELAY)) < DateTime.now() )
             # if ok_to_create:
             #     send_account_creation_email(request, to_email)
@@ -522,19 +520,19 @@ def test_email(request):
     return r
 
 @role_required('Secretariat')
-def add_account_whitelist(request):
+def add_account_allowlist(request):
     success = False
     if request.method == 'POST':
-        form = WhitelistForm(request.POST)
+        form = AllowlistForm(request.POST)
         if form.is_valid():
             email = form.cleaned_data['email']
-            entry = Whitelisted(email=email, by=request.user.person)
+            entry = Allowlisted(email=email, by=request.user.person)
             entry.save()
             success = True
     else:
-        form = WhitelistForm()
+        form = AllowlistForm()
 
-    return render(request, 'ietfauth/whitelist_form.html', {
+    return render(request, 'ietfauth/allowlist_form.html', {
         'form': form,
         'success': success,
     })
diff --git a/ietf/ipr/tests.py b/ietf/ipr/tests.py
index a190306b1..fadbb4290 100644
--- a/ietf/ipr/tests.py
+++ b/ietf/ipr/tests.py
@@ -134,7 +134,7 @@ class IprTests(TestCase):
     def test_search(self):
         WgDraftFactory() # The test matching the prefix "draft" needs more than one thing to find
         draft = WgDraftFactory()
-        ipr = HolderIprDisclosureFactory(docs=[draft,],patent_info='Number: US12345\nTitle: A method of transfering bits\nInventor: A. Nonymous\nDate: 2000-01-01')
+        ipr = HolderIprDisclosureFactory(docs=[draft,],patent_info='Number: US12345\nTitle: A method of transferring bits\nInventor: A. Nonymous\nDate: 2000-01-01')
 
         url = urlreverse("ietf.ipr.views.search")
 
@@ -262,7 +262,7 @@ class IprTests(TestCase):
             "iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
             "patent_number": "SE12345678901",
             "patent_inventor": "A. Nonymous",
-            "patent_title": "A method of transfering bits",
+            "patent_title": "A method of transferring bits",
             "patent_date": "2000-01-01",
             "has_patent_pending": False,
             "licensing": "royalty-free",
@@ -277,7 +277,7 @@ class IprTests(TestCase):
         ipr = iprs[0]
         self.assertEqual(ipr.holder_legal_name, "Test Legal")
         self.assertEqual(ipr.state.slug, 'pending')
-        for item in ['SE12345678901','A method of transfering bits','2000-01-01']:
+        for item in ['SE12345678901','A method of transferring bits','2000-01-01']:
             self.assertIn(item, ipr.get_child().patent_info)
         self.assertTrue(isinstance(ipr.get_child(),HolderIprDisclosure))
         self.assertEqual(len(outbox),1)
@@ -318,7 +318,7 @@ class IprTests(TestCase):
             "iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
             "patent_number": "SE12345678901",
             "patent_inventor": "A. Nonymous",
-            "patent_title": "A method of transfering bits",
+            "patent_title": "A method of transferring bits",
             "patent_date": "2000-01-01",
             "has_patent_pending": False,
             "licensing": "royalty-free",
@@ -332,7 +332,7 @@ class IprTests(TestCase):
         ipr = iprs[0]
         self.assertEqual(ipr.holder_legal_name, "Test Legal")
         self.assertEqual(ipr.state.slug, "pending")
-        for item in ['SE12345678901','A method of transfering bits','2000-01-01' ]:
+        for item in ['SE12345678901','A method of transferring bits','2000-01-01' ]:
             self.assertIn(item, ipr.get_child().patent_info)
         self.assertTrue(isinstance(ipr.get_child(),ThirdPartyIprDisclosure))
         self.assertEqual(len(outbox),1)
@@ -368,7 +368,7 @@ class IprTests(TestCase):
             "patent_date": "2000-01-01",
             "patent_inventor": "A. Nonymous",
             "patent_number": "SE12345678901",
-            "patent_title": "A method of transfering bits",
+            "patent_title": "A method of transferring bits",
             "submitter_email": "test@holder.com",
             "submitter_name": "Test Holder",
             "updates": [],
@@ -414,7 +414,7 @@ class IprTests(TestCase):
             "iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
             "patent_number": "SE12345678901",
             "patent_inventor": "A. Nonymous",
-            "patent_title": "A method of transfering bits",
+            "patent_title": "A method of transferring bits",
             "patent_date": "2000-01-01",
             "has_patent_pending": False,
             "licensing": "royalty-free",
@@ -450,7 +450,7 @@ class IprTests(TestCase):
             "iprdocrel_set-0-revisions": '00',
             "patent_number": "SE12345678901",
             "patent_inventor": "A. Nonymous",
-            "patent_title": "A method of transfering bits",
+            "patent_title": "A method of transferring bits",
             "patent_date": "2000-01-01",
             "has_patent_pending": False,
             "licensing": "royalty-free",
@@ -753,4 +753,4 @@ Subject: test
         removed_docevent = doc.docevent_set.filter(type='removed_related_ipr').first()
         self.assertIn(ipr.title, removed_docevent.desc,
                       'IprDisclosure title does not appear in DocEvent desc when removed')
-        
\ No newline at end of file
+        
diff --git a/ietf/liaisons/forms.py b/ietf/liaisons/forms.py
index 59e96e3cd..fa1f550d0 100644
--- a/ietf/liaisons/forms.py
+++ b/ietf/liaisons/forms.py
@@ -471,7 +471,6 @@ class IncomingLiaisonForm(LiaisonModelForm):
 
 
 class OutgoingLiaisonForm(LiaisonModelForm):
-    from_contact = SearchableEmailField(only_users=True)
     approved = forms.BooleanField(label="Obtained prior approval", required=False)
 
     class Meta:
@@ -501,6 +500,7 @@ class OutgoingLiaisonForm(LiaisonModelForm):
             self.fields['from_groups'].initial = [flat_choices[0][0]]
         
         if has_role(self.user, "Secretariat"):
+            self.fields['from_contact'] = SearchableEmailField(only_users=True)  # secretariat can edit this field!
             return
 
         if self.person.role_set.filter(name='liaiman',group__state='active'):
@@ -509,8 +509,10 @@ class OutgoingLiaisonForm(LiaisonModelForm):
             email = self.person.role_set.filter(name__in=('ad','chair'),group__state='active').first().email.address
         else:
             email = self.person.email_address()
+
+        # Non-secretariat user cannot change the from_contact field. Fill in its value.
+        self.fields['from_contact'].disabled = True
         self.fields['from_contact'].initial = email
-        self.fields['from_contact'].widget.attrs['readonly'] = True
 
     def set_to_fields(self):
         '''Set to_groups and to_contacts options and initial value based on user
diff --git a/ietf/mailinglists/admin.py b/ietf/mailinglists/admin.py
index aaa086823..90efaf9c9 100644
--- a/ietf/mailinglists/admin.py
+++ b/ietf/mailinglists/admin.py
@@ -2,7 +2,7 @@
 
 from django.contrib import admin
 
-from ietf.mailinglists.models import List, Subscribed, Whitelisted
+from ietf.mailinglists.models import List, Subscribed, Allowlisted
 
 
 class ListAdmin(admin.ModelAdmin):
@@ -18,6 +18,6 @@ class SubscribedAdmin(admin.ModelAdmin):
 admin.site.register(Subscribed, SubscribedAdmin)
 
 
-class WhitelistedAdmin(admin.ModelAdmin):
+class AllowlistedAdmin(admin.ModelAdmin):
     list_display = ('id', 'time', 'email', 'by')
-admin.site.register(Whitelisted, WhitelistedAdmin)
+admin.site.register(Allowlisted, AllowlistedAdmin)
diff --git a/ietf/mailinglists/migrations/0003_allowlisted.py b/ietf/mailinglists/migrations/0003_allowlisted.py
new file mode 100644
index 000000000..a3f098d9c
--- /dev/null
+++ b/ietf/mailinglists/migrations/0003_allowlisted.py
@@ -0,0 +1,22 @@
+# Generated by Django 2.2.28 on 2022-12-05 14:26
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('person', '0029_use_timezone_now_for_person_models'),
+        ('mailinglists', '0002_auto_20190703_1344'),
+    ]
+
+    operations = [
+        migrations.RenameModel(
+            old_name='Whitelisted',
+            new_name='Allowlisted',
+        ),
+        migrations.AlterModelOptions(
+            name='allowlisted',
+            options={'verbose_name_plural': 'Allowlisted'},
+        ),
+    ]
diff --git a/ietf/mailinglists/models.py b/ietf/mailinglists/models.py
index 606aab624..21f3a7671 100644
--- a/ietf/mailinglists/models.py
+++ b/ietf/mailinglists/models.py
@@ -28,12 +28,12 @@ class Subscribed(models.Model):
     class Meta:
         verbose_name_plural = "Subscribed"
 
-class Whitelisted(models.Model):
+class Allowlisted(models.Model):
     time = models.DateTimeField(auto_now_add=True)
     email = models.CharField("Email address", max_length=64, validators=[validate_email])
     by = ForeignKey(Person)
     def __str__(self):
-        return "" % (self.email, self.time)
+        return "" % (self.email, self.time)
     class Meta:
-        verbose_name_plural = "Whitelisted"
+        verbose_name_plural = "Allowlisted"
 
diff --git a/ietf/mailinglists/resources.py b/ietf/mailinglists/resources.py
index b2fce0900..018a8327b 100644
--- a/ietf/mailinglists/resources.py
+++ b/ietf/mailinglists/resources.py
@@ -11,17 +11,17 @@ from tastypie.cache import SimpleCache
 from ietf import api
 from ietf.api import ToOneField                         # pyflakes:ignore
 
-from ietf.mailinglists.models import Whitelisted, List, Subscribed
+from ietf.mailinglists.models import Allowlisted, List, Subscribed
 
 
 from ietf.person.resources import PersonResource
-class WhitelistedResource(ModelResource):
+class AllowlistedResource(ModelResource):
     by               = ToOneField(PersonResource, 'by')
     class Meta:
-        queryset = Whitelisted.objects.all()
+        queryset = Allowlisted.objects.all()
         serializer = api.Serializer()
         cache = SimpleCache()
-        #resource_name = 'whitelisted'
+        #resource_name = 'allowlisted'
         ordering = ['id', ]
         filtering = { 
             "id": ALL,
@@ -29,7 +29,7 @@ class WhitelistedResource(ModelResource):
             "email": ALL,
             "by": ALL_WITH_RELATIONS,
         }
-api.mailinglists.register(WhitelistedResource())
+api.mailinglists.register(AllowlistedResource())
 
 class ListResource(ModelResource):
     class Meta:
diff --git a/ietf/meeting/forms.py b/ietf/meeting/forms.py
index a946fe20b..477a1dbb9 100644
--- a/ietf/meeting/forms.py
+++ b/ietf/meeting/forms.py
@@ -223,10 +223,10 @@ class InterimMeetingModelForm(forms.ModelForm):
 
 class InterimSessionModelForm(forms.ModelForm):
     date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1"}, label='Date', required=False)
-    time = forms.TimeField(widget=forms.TimeInput(format='%H:%M'), required=True, help_text="Local time")
+    time = forms.TimeField(widget=forms.TimeInput(format='%H:%M'), required=True, help_text="Start time in meeting time zone")
     time.widget.attrs['placeholder'] = "HH:MM"
     requested_duration = CustomDurationField(required=True)
-    end_time = forms.TimeField(required=False, help_text="Local time")
+    end_time = forms.TimeField(required=False, help_text="End time in meeting time zone")
     end_time.widget.attrs['placeholder'] = "HH:MM"
     remote_participation = forms.ChoiceField(choices=(), required=False)
     remote_instructions = forms.CharField(
@@ -258,8 +258,8 @@ class InterimSessionModelForm(forms.ModelForm):
         self.is_edit = bool(self.instance.pk)
         # setup fields that aren't intrinsic to the Session object
         if self.is_edit:
-            self.initial['date'] = self.instance.official_timeslotassignment().timeslot.time
-            self.initial['time'] = self.instance.official_timeslotassignment().timeslot.time
+            self.initial['date'] = self.instance.official_timeslotassignment().timeslot.local_start_time().date()
+            self.initial['time'] = self.instance.official_timeslotassignment().timeslot.local_start_time().time()
             if self.instance.agenda():
                 doc = self.instance.agenda()
                 content = doc.text_or_error()
diff --git a/ietf/meeting/helpers.py b/ietf/meeting/helpers.py
index fab10fad2..96143076b 100644
--- a/ietf/meeting/helpers.py
+++ b/ietf/meeting/helpers.py
@@ -256,6 +256,10 @@ class AgendaFilterOrganizer(AgendaKeywordTool):
         self.special_filters = None
         if self._use_legacy_keywords():
             self.extra_labels += ('Plenary',)  # need this when not using session purpose
+        self.manual_extra_labels = set()
+
+    def add_extra_filter(self, kw):
+        self.manual_extra_labels.add(kw)
 
     def get_non_area_keywords(self):
         """Get list of any 'non-area' (aka 'special') keywords
@@ -436,13 +440,13 @@ class AgendaFilterOrganizer(AgendaKeywordTool):
     def _extra_filters(self):
         """Get list of filters corresponding to self.extra_labels"""
         item_source = self.assignments or self.sessions or []
-        candidates = set(self.extra_labels)
+        candidates = set(self.extra_labels).union(self.manual_extra_labels)
         return self._filter_column(
             label=None,
             keyword=None,
             children=[
                 self._filter_entry(label=label, keyword=xslugify(label), toggled_by=[], is_bof=False)
-                for label in candidates if any(
+                for label in candidates if label in self.manual_extra_labels or any(
                     # Keep only those that will affect at least one session
                     [label.lower() in item.filter_keywords for item in item_source]
                 )]
@@ -1056,8 +1060,7 @@ def sessions_post_save(request, forms):
                 by=request.user.person,
             )
         
-        if ('date' in form.changed_data) or ('time' in form.changed_data):
-            update_interim_session_assignment(form)
+        update_interim_session_assignment(form)
         if 'agenda' in form.changed_data:
             form.save_agenda()
 
@@ -1136,6 +1139,8 @@ def update_interim_session_assignment(form):
     """Helper function to create / update timeslot assigned to interim session
 
     form is an InterimSessionModelForm
+
+    Only updates timeslot time (a datetime) and duration
     """
     session = form.instance
     meeting = session.meeting
@@ -1144,9 +1149,10 @@ def update_interim_session_assignment(form):
     )
     if session.official_timeslotassignment():
         slot = session.official_timeslotassignment().timeslot
-        slot.time = time
-        slot.duration = session.requested_duration
-        slot.save()
+        if slot.time != time or slot.duration != session.requested_duration:
+            slot.time = time
+            slot.duration = session.requested_duration
+            slot.save()
     else:
         slot = TimeSlot.objects.create(
             meeting=meeting,
diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py
index 8596ad975..603fa1695 100644
--- a/ietf/meeting/models.py
+++ b/ietf/meeting/models.py
@@ -39,7 +39,7 @@ from ietf.name.models import (
 )
 from ietf.person.models import Person
 from ietf.utils.decorators import memoize
-from ietf.utils.history import find_history_replacements_active_at
+from ietf.utils.history import find_history_replacements_active_at, find_history_active_at
 from ietf.utils.storage import NoLocationMigrationFileSystemStorage
 from ietf.utils.text import xslugify
 from ietf.utils.timezone import datetime_from_date, date_today
@@ -409,18 +409,36 @@ class Meeting(models.Model):
     def uses_notes(self):
         return self.date>=datetime.date(2020,7,6)
 
-    def groups_at_the_time(self):
+    def meeting_start(self):
+        """Meeting-local midnight at the start of the meeting date"""
+        return self.tz().localize(datetime.datetime.combine(self.date, datetime.time()))
+
+    def _groups_at_the_time(self):
+        """Get dict mapping Group PK to appropriate Group or GroupHistory at meeting time
+
+        Known issue: only looks up Groups and their *current* parents when called. If a Group's
+        parent was different at meeting time, that parent will not be in the cache. Use
+        group_at_the_time() to look up values - that will fill in missing groups for you.
+        """
         if not hasattr(self,'cached_groups_at_the_time'):
             all_group_pks = set(self.session_set.values_list('group__pk', flat=True))
             all_group_pks.update(self.session_set.values_list('group__parent__pk', flat=True))
             all_group_pks.discard(None)
-            # meeting_time is meeting-local midnight at the start of the meeting date
-            meeting_start = self.tz().localize(
-                datetime.datetime.combine(self.date, datetime.time())
+            self.cached_groups_at_the_time = find_history_replacements_active_at(
+                Group.objects.filter(pk__in=all_group_pks),
+                self.meeting_start(),
             )
-            self.cached_groups_at_the_time = find_history_replacements_active_at(Group.objects.filter(pk__in=all_group_pks), meeting_start)
         return self.cached_groups_at_the_time
 
+    def group_at_the_time(self, group):
+        # MUST call self._groups_at_the_time() before assuming cached_groups_at_the_time exists
+        gatt = self._groups_at_the_time()
+        if group.pk in gatt:
+            return gatt[group.pk]
+        # Cache miss - look up the missing historical group and add it to the cache.
+        new_item = find_history_active_at(group, self.meeting_start()) or group  # fall back to original if no history
+        self.cached_groups_at_the_time[group.pk] = new_item
+        return new_item
 
     class Meta:
         ordering = ["-date", "-id"]
@@ -1301,11 +1319,12 @@ class Session(models.Model):
         return urljoin(settings.IETF_NOTES_URL, self.notes_id())
 
     def group_at_the_time(self):
-        return self.meeting.groups_at_the_time()[self.group.pk]
+        return self.meeting.group_at_the_time(self.group)
 
     def group_parent_at_the_time(self):
         if self.group_at_the_time().parent:
-            return self.meeting.groups_at_the_time()[self.group_at_the_time().parent.pk]
+            return self.meeting.group_at_the_time(self.group_at_the_time().parent)
+
 
 class SchedulingEvent(models.Model):
     session = ForeignKey(Session)
diff --git a/ietf/meeting/tests_forms.py b/ietf/meeting/tests_forms.py
index 4a06d7786..61d0a1d2c 100644
--- a/ietf/meeting/tests_forms.py
+++ b/ietf/meeting/tests_forms.py
@@ -6,6 +6,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile
 from django.test import override_settings, RequestFactory
 
 from ietf.group.factories import GroupFactory
+from ietf.meeting.factories import SessionFactory
 from ietf.meeting.forms import (FileUploadForm, ApplyToAllFileUploadForm, InterimSessionModelForm,
                                 InterimMeetingModelForm)
 from ietf.person.factories import PersonFactory
@@ -123,6 +124,15 @@ class InterimSessionModelFormTests(TestCase):
         self.assertNotIn('meetecho', choice_vals)
         self.assertIn('manual', choice_vals)
 
+    def test_edits_in_meeting_time_zone(self):
+        # use a time zone that never has a UTC offset of 0, even with DST
+        session = SessionFactory(meeting__type_id='interim', meeting__time_zone='America/Halifax')
+        form = InterimSessionModelForm(instance=session)
+        self.assertEqual(
+            form.initial['time'].strftime('%H:%M'),
+            session.official_timeslotassignment().timeslot.local_start_time().strftime('%H:%M'),
+        )
+
 
 class InterimMeetingModelFormTests(TestCase):
     def test_enforces_authroles(self):
diff --git a/ietf/meeting/tests_helpers.py b/ietf/meeting/tests_helpers.py
index d9dafb7d6..9ce3c21cb 100644
--- a/ietf/meeting/tests_helpers.py
+++ b/ietf/meeting/tests_helpers.py
@@ -590,26 +590,26 @@ class InterimTests(TestCase):
         mock_form.changed_data = []
         mock_form.requires_approval = True
 
-        mock_form.cleaned_data = {'remote_participation': None}
+        mock_form.cleaned_data = {'date': date_today(), 'time': datetime.time(1, 23), 'remote_participation': None}
         sessions_post_save(RequestFactory().post('/some/url'), [mock_form])
         self.assertTrue(mock_create_method.called)
         self.assertCountEqual(mock_create_method.call_args[0][0], [])
 
         mock_create_method.reset_mock()
-        mock_form.cleaned_data = {'remote_participation': 'manual'}
+        mock_form.cleaned_data['remote_participation'] = 'manual'
         sessions_post_save(RequestFactory().post('/some/url'), [mock_form])
         self.assertTrue(mock_create_method.called)
         self.assertCountEqual(mock_create_method.call_args[0][0], [])
 
         mock_create_method.reset_mock()
-        mock_form.cleaned_data = {'remote_participation': 'meetecho'}
+        mock_form.cleaned_data['remote_participation'] = 'meetecho'
         sessions_post_save(RequestFactory().post('/some/url'), [mock_form])
         self.assertTrue(mock_create_method.called)
         self.assertCountEqual(mock_create_method.call_args[0][0], [session])
 
         # Check that an exception does not percolate through sessions_post_save
         mock_create_method.side_effect = RuntimeError('some error')
-        mock_form.cleaned_data = {'remote_participation': 'meetecho'}
+        mock_form.cleaned_data['remote_participation'] = 'meetecho'
         # create mock request with session / message storage
         request = RequestFactory().post('/some/url')
         setattr(request, 'session', 'session')
diff --git a/ietf/meeting/tests_js.py b/ietf/meeting/tests_js.py
index 607f28123..0e6a42343 100644
--- a/ietf/meeting/tests_js.py
+++ b/ietf/meeting/tests_js.py
@@ -1076,9 +1076,9 @@ class InterimTests(IetfSeleniumTestCase):
                     unexpected.add(entry.text)
             advance_month()
 
-        self.assertEqual(seen, visible_meetings, "Expected calendar entries not shown.")
-        self.assertEqual(not_visible, set(), "Hidden calendar entries for expected interim meetings.")
-        self.assertEqual(unexpected, set(), "Unexpected calendar entries visible")
+        self.assertCountEqual(seen, visible_meetings, "Expected calendar entries not shown.")
+        self.assertCountEqual(not_visible, set(), "Hidden calendar entries for expected interim meetings.")
+        self.assertCountEqual(unexpected, set(), "Unexpected calendar entries visible")
 
     def do_upcoming_view_filter_test(self, querystring, visible_meetings=()):
         self.login()
@@ -1152,102 +1152,85 @@ class InterimTests(IetfSeleniumTestCase):
         ietf_meetings = set(self.all_ietf_meetings())
         self.do_upcoming_view_filter_test('', ietf_meetings.union(self.displayed_interims()))
 
+    def test_upcoming_view_show_ietf_meetings(self):
+        self.do_upcoming_view_filter_test('?show=ietf-meetings', self.all_ietf_meetings())
+
     def test_upcoming_view_filter_show_group(self):
         # Show none
-        ietf_meetings = set(self.all_ietf_meetings())
-        self.do_upcoming_view_filter_test('?show=', ietf_meetings)
+        self.do_upcoming_view_filter_test('?show=')
 
         # Show one
-        self.do_upcoming_view_filter_test('?show=mars', 
-                                          ietf_meetings.union(
-                                              self.displayed_interims(groups=['mars'])
-                                          ))
+        self.do_upcoming_view_filter_test('?show=mars', self.displayed_interims(groups=['mars']))
 
         # Show two
-        self.do_upcoming_view_filter_test('?show=mars,ames', 
-                                          ietf_meetings.union(
-                                              self.displayed_interims(groups=['mars', 'ames'])
-                                          ))
+        self.do_upcoming_view_filter_test('?show=mars,ames',self.displayed_interims(groups=['mars', 'ames']))
+
+        # Show two plus ietf-meetings
+        self.do_upcoming_view_filter_test(
+            '?show=ietf-meetings,mars,ames',
+            set(self.all_ietf_meetings()).union(self.displayed_interims(groups=['mars', 'ames']))
+        )
 
     def test_upcoming_view_filter_show_area(self):
         mars = Group.objects.get(acronym='mars')
         area = mars.parent
-        ietf_meetings = set(self.all_ietf_meetings())
-        self.do_upcoming_view_filter_test('?show=%s' % area.acronym,
-                                          ietf_meetings.union(
-                                              self.displayed_interims(groups=['mars', 'ames'])
-                                          ))
+        self.do_upcoming_view_filter_test('?show=%s' % area.acronym, self.displayed_interims(groups=['mars', 'ames']))
 
     def test_upcoming_view_filter_show_type(self):
-        ietf_meetings = set(self.all_ietf_meetings())
-        self.do_upcoming_view_filter_test('?show=plenary',
-                                          ietf_meetings.union(
-                                              self.displayed_interims(groups=['sg'])
-                                          ))
+        self.do_upcoming_view_filter_test('?show=plenary', self.displayed_interims(groups=['sg']))
 
     def test_upcoming_view_filter_hide_group(self):
         mars = Group.objects.get(acronym='mars')
         area = mars.parent
 
         # Without anything shown, should see only ietf meetings
-        ietf_meetings = set(self.all_ietf_meetings())
-        self.do_upcoming_view_filter_test('?hide=mars', ietf_meetings)
+        self.do_upcoming_view_filter_test('?hide=mars')
 
         # With group shown
-        self.do_upcoming_view_filter_test('?show=ames,mars&hide=mars',
-                                          ietf_meetings.union(
-                                              self.displayed_interims(groups=['ames'])
-                                          ))
+        self.do_upcoming_view_filter_test('?show=ames,mars&hide=mars', self.displayed_interims(groups=['ames']))
         # With area shown
-        self.do_upcoming_view_filter_test('?show=%s&hide=mars' % area.acronym, 
-                                          ietf_meetings.union(
-                                              self.displayed_interims(groups=['ames'])
-                                          ))
-
+        self.do_upcoming_view_filter_test('?show=%s&hide=mars' % area.acronym, self.displayed_interims(groups=['ames']))
         # With type shown
-        self.do_upcoming_view_filter_test('?show=plenary&hide=sg',
-                                          ietf_meetings)
+        self.do_upcoming_view_filter_test('?show=plenary&hide=sg')
 
     def test_upcoming_view_filter_hide_area(self):
         mars = Group.objects.get(acronym='mars')
         area = mars.parent
 
-        # Without anything shown, should see only ietf meetings
-        ietf_meetings = set(self.all_ietf_meetings())
-        self.do_upcoming_view_filter_test('?hide=%s' % area.acronym, ietf_meetings)
+        # Without anything shown, should see nothing
+        self.do_upcoming_view_filter_test('?hide=%s' % area.acronym)
 
         # With area shown
-        self.do_upcoming_view_filter_test('?show=%s&hide=%s' % (area.acronym, area.acronym),
-                                          ietf_meetings)
+        self.do_upcoming_view_filter_test('?show=%s&hide=%s' % (area.acronym, area.acronym))
 
         # With group shown
-        self.do_upcoming_view_filter_test('?show=mars&hide=%s' % area.acronym, ietf_meetings)
+        self.do_upcoming_view_filter_test('?show=mars&hide=%s' % area.acronym)
 
         # With type shown
-        self.do_upcoming_view_filter_test('?show=regular&hide=%s' % area.acronym, ietf_meetings)
+        self.do_upcoming_view_filter_test('?show=regular&hide=%s' % area.acronym)
+
+        # with IETF meetings shown
+        self.do_upcoming_view_filter_test('?show=ietf-meetings,hide=%s' % area.acronym, self.all_ietf_meetings())
 
     def test_upcoming_view_filter_hide_type(self):
-        mars = Group.objects.get(acronym='mars')
-        area = mars.parent
-
-        # Without anything shown, should see only ietf meetings
-        ietf_meetings = set(self.all_ietf_meetings())
-        self.do_upcoming_view_filter_test('?hide=regular', ietf_meetings)
+        # Without anything shown, should see nothing
+        self.do_upcoming_view_filter_test('?hide=regular')
 
         # With group shown
-        self.do_upcoming_view_filter_test('?show=mars&hide=regular', ietf_meetings)
+        self.do_upcoming_view_filter_test('?show=mars&hide=regular')
 
         # With type shown
-        self.do_upcoming_view_filter_test('?show=plenary,regular&hide=%s' % area.acronym, 
-                                          ietf_meetings.union(
-                                              self.displayed_interims(groups=['sg'])
-                                          ))
+        self.do_upcoming_view_filter_test(
+            '?show=plenary,regular&hide=regular',
+            self.displayed_interims(groups=['sg'])
+        )
+
+        # With interim-meetings shown
+        self.do_upcoming_view_filter_test('?show=plenary,regular&hide=regular', self.displayed_interims(groups=['sg']))
 
     def test_upcoming_view_filter_whitespace(self):
         """Whitespace in filter lists should be ignored"""
-        meetings = set(self.all_ietf_meetings())
-        meetings.update(self.displayed_interims(groups=['mars']))
-        self.do_upcoming_view_filter_test('?show=mars , ames &hide=   ames', meetings)
+        self.do_upcoming_view_filter_test('?show=mars , ames &hide=   ames', self.displayed_interims(groups=['mars']))
 
     def test_upcoming_view_time_zone_selection(self):
         def _assert_interim_tz_correct(sessions, tz):
diff --git a/ietf/meeting/tests_models.py b/ietf/meeting/tests_models.py
index 175236611..8ea0d0c5b 100644
--- a/ietf/meeting/tests_models.py
+++ b/ietf/meeting/tests_models.py
@@ -5,9 +5,11 @@ import datetime
 
 from mock import patch
 
+from ietf.group.factories import GroupFactory, GroupHistoryFactory
 from ietf.meeting.factories import MeetingFactory, SessionFactory, AttendedFactory
 from ietf.stats.factories import MeetingRegistrationFactory
 from ietf.utils.test_utils import TestCase
+from ietf.utils.timezone import date_today, datetime_today
 
 
 class MeetingTests(TestCase):
@@ -104,6 +106,14 @@ class MeetingTests(TestCase):
             vtz = meeting.vtimezone()
         self.assertIsNone(vtz)
 
+    def test_group_at_the_time(self):
+        m = MeetingFactory(type_id='ietf', date=date_today() - datetime.timedelta(days=10))
+        cached_groups = GroupFactory.create_batch(2)
+        m.cached_groups_at_the_time = {g.pk: g for g in cached_groups}  # fake the cache
+        uncached_group_hist = GroupHistoryFactory(time=datetime_today() - datetime.timedelta(days=30))
+        self.assertEqual(m.group_at_the_time(uncached_group_hist.group), uncached_group_hist)
+        self.assertIn(uncached_group_hist.group.pk, m.cached_groups_at_the_time)
+
 
 class SessionTests(TestCase):
     def test_chat_archive_url_with_jabber(self):
diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py
index 673faba58..9d7fc8f28 100644
--- a/ietf/meeting/tests_views.py
+++ b/ietf/meeting/tests_views.py
@@ -4507,8 +4507,16 @@ class InterimTests(TestCase):
         # Just a quick check of functionality - details tested by test_js.InterimTests
         make_meeting_test_data(create_interims=True)
         url = urlreverse("ietf.meeting.views.upcoming_ical")
-        r = self.client.get(url + '?show=mars')
 
+        r = self.client.get(url + '?show=mars')
+        self.assertEqual(r.status_code, 200)
+        assert_ical_response_is_valid(self, r,
+                                      expected_event_summaries=[
+                                          'mars - Martian Special Interest Group',
+                                      ],
+                                      expected_event_count=1)
+
+        r = self.client.get(url + '?show=mars,ietf-meetings')
         self.assertEqual(r.status_code, 200)
         assert_ical_response_is_valid(self, r,
                                       expected_event_summaries=[
@@ -5374,12 +5382,11 @@ class InterimTests(TestCase):
         length_before = len(outbox)
         form_initial = r.context['form'].initial
         formset_initial =  r.context['formset'].forms[0].initial
-        new_time = formset_initial['time'] + datetime.timedelta(hours=1)
         data = {'group':group.pk,
                 'meeting_type':'single',
                 'session_set-0-id':meeting.session_set.first().id,
                 'session_set-0-date':formset_initial['date'].strftime('%Y-%m-%d'),
-                'session_set-0-time':new_time.strftime('%H:%M'),
+                'session_set-0-time':'12:34',
                 'session_set-0-requested_duration': '00:30',
                 'session_set-0-remote_instructions':formset_initial['remote_instructions'],
                 #'session_set-0-agenda':formset_initial['agenda'],
@@ -5392,7 +5399,10 @@ class InterimTests(TestCase):
         self.assertEqual(len(outbox),length_before)
         session = meeting.session_set.first()
         timeslot = session.official_timeslotassignment().timeslot
-        self.assertEqual(timeslot.time,new_time)
+        self.assertEqual(
+            timeslot.time,
+            meeting.tz().localize(datetime.datetime.combine(formset_initial['date'], datetime.time(12, 34))),
+        )
         
     def test_interim_request_edit(self):
         '''Edit request.  Send notice of change'''
@@ -5412,13 +5422,12 @@ class InterimTests(TestCase):
         length_before = len(outbox)
         form_initial = r.context['form'].initial
         formset_initial =  r.context['formset'].forms[0].initial
-        new_time = formset_initial['time'] + datetime.timedelta(hours=1)
         new_duration = formset_initial['requested_duration'] + datetime.timedelta(hours=1)
         data = {'group':group.pk,
                 'meeting_type':'single',
                 'session_set-0-id':meeting.session_set.first().id,
                 'session_set-0-date':formset_initial['date'].strftime('%Y-%m-%d'),
-                'session_set-0-time':new_time.strftime('%H:%M'),
+                'session_set-0-time': '12:34',
                 'session_set-0-requested_duration':self.strfdelta(new_duration, '{hours}:{minutes}'),
                 'session_set-0-remote_instructions':formset_initial['remote_instructions'],
                 #'session_set-0-agenda':formset_initial['agenda'],
@@ -5432,7 +5441,10 @@ class InterimTests(TestCase):
         self.assertIn('CHANGED', outbox[-1]['Subject'])
         session = meeting.session_set.first()
         timeslot = session.official_timeslotassignment().timeslot
-        self.assertEqual(timeslot.time,new_time)
+        self.assertEqual(
+            timeslot.time,
+            meeting.tz().localize(datetime.datetime.combine(formset_initial['date'], datetime.time(12, 34))),
+        )
         self.assertEqual(timeslot.duration,new_duration)
     
     def strfdelta(self, tdelta, fmt):
diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py
index 6fe1e95e8..d269bbd70 100644
--- a/ietf/meeting/views.py
+++ b/ietf/meeting/views.py
@@ -3456,6 +3456,10 @@ def upcoming(request):
     # Set up for agenda filtering - only one filter_category here
     AgendaKeywordTagger(sessions=interim_sessions).apply()
     filter_organizer = AgendaFilterOrganizer(sessions=interim_sessions, single_category=True)
+    # Allow filtering to show only IETF Meetings. This adds a button labeled "IETF Meetings" to the
+    # "Other" column of the filter UI. When enabled, this adds the keyword "ietf-meetings" to the "show"
+    # filter list. The IETF meetings are explicitly labeled with this keyword in upcoming.html.
+    filter_organizer.add_extra_filter('IETF Meetings')
 
     entries = list(ietf_meetings)
     entries.extend(list(interim_sessions))
@@ -3544,9 +3548,14 @@ def upcoming_ical(request):
             a.session = sessions.get(a.session_id) or a.session
             a.session.ical_status = ical_session_status(a)
 
-    # handle IETFs separately
-    ietfs = [m for m in meetings if m.type_id == 'ietf']
-    preprocess_meeting_important_dates(ietfs)
+    # Handle IETFs separately. Manually apply the 'ietf-meetings' filter.
+    if filter_params is None or (
+            'ietf-meetings' in filter_params['show'] and 'ietf-meetings' not in filter_params['hide']
+    ):
+        ietfs = [m for m in meetings if m.type_id == 'ietf']
+        preprocess_meeting_important_dates(ietfs)
+    else:
+        ietfs = []
 
     meeting_vtz = {meeting.vtimezone() for meeting in meetings}
     meeting_vtz.discard(None)
diff --git a/ietf/name/fixtures/names.json b/ietf/name/fixtures/names.json
index b17cf80ba..06dc24188 100644
--- a/ietf/name/fixtures/names.json
+++ b/ietf/name/fixtures/names.json
@@ -385,7 +385,7 @@
   },
   {
     "fields": {
-      "desc": "The document is in the RFC editor Queue (as confirmed by http://www.rfc-editor.org/queue.html).",
+      "desc": "The document is in the RFC editor Queue (as confirmed by https://www.rfc-editor.org/queue.html).",
       "name": "RFC Ed Queue",
       "next_states": [
         7
@@ -2585,7 +2585,7 @@
   },
   {
     "fields": {
-      "label": "Liason Statement State"
+      "label": "Liaison Statement State"
     },
     "model": "doc.statetype",
     "pk": "liaison"
@@ -3844,7 +3844,7 @@
   {
     "fields": {
       "cc": [],
-      "desc": "Recipients for message to adminstrators when a charter state edit needs followon administrative action",
+      "desc": "Recipients for message to administrators when a charter state edit needs follow-on administrative action",
       "to": [
         "iesg_secretary"
       ]
@@ -5909,7 +5909,7 @@
   },
   {
     "fields": {
-      "desc": "The set of people who can approve this liasion statemetns",
+      "desc": "The set of people who can approve this liaison statements",
       "template": "{{liaison.approver_emails|join:\", \"}}"
     },
     "model": "mailtrigger.recipient",
diff --git a/ietf/nomcom/forms.py b/ietf/nomcom/forms.py
index f0b00a485..20bf508e8 100644
--- a/ietf/nomcom/forms.py
+++ b/ietf/nomcom/forms.py
@@ -175,7 +175,7 @@ class MergeNomineeForm(forms.Form):
         secondary_emails = self.cleaned_data.get("secondary_emails")
         if primary_email and secondary_emails:
             if primary_email in secondary_emails:
-                msg = "Primary and secondary email address must be differents"
+                msg = "Primary and secondary email address must be different"
                 self._errors["primary_email"] = self.error_class([msg])
         return self.cleaned_data
 
@@ -865,4 +865,4 @@ class VolunteerForm(forms.ModelForm):
          self.fields['nomcoms'].queryset = NomCom.objects.filter(is_accepting_volunteers=True).exclude(volunteer__person=person)
          self.fields['nomcoms'].help_text = 'You may volunteer even if the datatracker does not currently calculate that you are eligible. Eligibility will be assessed when the selection process is performed.'
          self.fields['affiliation'].help_text = 'Affiliation to show in the volunteer list'
-         self.fields['affiliation'].required = True
\ No newline at end of file
+         self.fields['affiliation'].required = True
diff --git a/ietf/person/factories.py b/ietf/person/factories.py
index 580c0bffe..8e80932c9 100644
--- a/ietf/person/factories.py
+++ b/ietf/person/factories.py
@@ -65,7 +65,10 @@ class PersonFactory(factory.django.DjangoModelFactory):
 
     user = factory.SubFactory(UserFactory)
     name = factory.LazyAttribute(lambda p: normalize_name('%s %s'%(p.user.first_name, p.user.last_name)))
-    ascii = factory.LazyAttribute(lambda p: force_text(unidecode_name(p.name)))
+    # Some i18n names, e.g., "शिला के.सी." have a dot at the end that is also part of the ASCII, e.g., "Shilaa Kesii."
+    # That trailing dot breaks extract_authors(). Avoid this issue by stripping the dot from the ASCII.
+    # Some others have a trailing semicolon (e.g., "உயிரோவியம் தங்கராஐ;") - strip those, too.
+    ascii = factory.LazyAttribute(lambda p: force_text(unidecode_name(p.name)).rstrip(".;"))
 
     class Params:
         with_bio = factory.Trait(biography = "\n\n".join(fake.paragraphs())) # type: ignore
diff --git a/ietf/person/management/commands/deactivate_email_addresses.py b/ietf/person/management/commands/deactivate_email_addresses.py
index c91761987..fea8d8ee8 100644
--- a/ietf/person/management/commands/deactivate_email_addresses.py
+++ b/ietf/person/management/commands/deactivate_email_addresses.py
@@ -84,7 +84,7 @@ class Command(BaseCommand):
                     email.origin = email.person.user.username if email.person.user_id else ('script: %s deactivation' % options['reason'])
                     email.save()
                     PersonEvent.objects.create(person=email.person, type='email_address_deactivated',
-                        desc="Deactivated the email addres <%s>. Reason: %s" % (email.address, options['reason']) )
+                        desc="Deactivated the email address <%s>. Reason: %s" % (email.address, options['reason']) )
                 else:
                     if email is None:
                         not_found.append(a)
diff --git a/ietf/secr/templates/telechat/group.html b/ietf/secr/templates/telechat/group.html
index 288316e90..890c451e8 100644
--- a/ietf/secr/templates/telechat/group.html
+++ b/ietf/secr/templates/telechat/group.html
@@ -5,7 +5,7 @@
          External Review NOT APPROVED;
         
The Secretariat will wait for instructions from
- The IESG decides the document needs more thime in INTERNAL REVIEW. The Secreatriat will put it back on the agenda for the next teleconference in the same category.
+ The IESG decides the document needs more time in INTERNAL REVIEW. The Secretariat will put it back on the agenda for the next teleconference in the same category.
The IESG has made changes since the charter was seen in INTERNAL REVIEW, and decides to send it back to INTERNAL REVIEW the charter again.
{% endif %} @@ -30,4 +30,4 @@ Yes, the charter is NOT APPROVED; The charter needs more work, or the IESG decides to shelve formation of the working group. "The Secretariat will await further instruction from regarding the rechartering of this working group."

{% endif %} - + \ No newline at end of file diff --git a/ietf/settings.py b/ietf/settings.py index 37ebab643..144f321cc 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -18,7 +18,7 @@ warnings.filterwarnings("ignore", message="The logout\(\) view is superseded by" warnings.filterwarnings("ignore", message="Report.file_reporters will no longer be available in Coverage.py 4.2", module="coverage.report") warnings.filterwarnings("ignore", message="{% load staticfiles %} is deprecated") warnings.filterwarnings("ignore", message="Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated", module="bleach") - +warnings.filterwarnings("ignore", message="HTTPResponse.getheader\(\) is deprecated", module='selenium.webdriver') try: import syslog syslog.openlog(str("datatracker"), syslog.LOG_PID, syslog.LOG_USER) @@ -542,7 +542,7 @@ INTERNAL_IPS = ( # no slash at end IDTRACKER_BASE_URL = "https://datatracker.ietf.org" -RFCDIFF_BASE_URL = "https://www.ietf.org/rfcdiff" +RFCDIFF_BASE_URL = "https://author-tools.ietf.org/iddiff" IDNITS_BASE_URL = "https://author-tools.ietf.org/api/idnits" IDNITS_SERVICE_URL = "https://author-tools.ietf.org/idnits" @@ -610,7 +610,7 @@ TEST_TEMPLATE_IGNORE = [ "500.html" # isn't loaded by regular loader, but checked by test_500_page() ] -TEST_COVERAGE_MASTER_FILE = os.path.join(BASE_DIR, "../release-coverage.json") +TEST_COVERAGE_MAIN_FILE = os.path.join(BASE_DIR, "../release-coverage.json") TEST_COVERAGE_LATEST_FILE = os.path.join(BASE_DIR, "../latest-coverage.json") TEST_CODE_COVERAGE_CHECKER = None @@ -680,7 +680,7 @@ INTERNET_ALL_DRAFTS_ARCHIVE_DIR = '/a/ietfdata/doc/draft/archive' MEETING_RECORDINGS_DIR = '/a/www/audio' DERIVED_DIR = '/a/ietfdata/derived' -DOCUMENT_FORMAT_WHITELIST = ["txt", "ps", "pdf", "xml", "html", ] +DOCUMENT_FORMAT_ALLOWLIST = ["txt", "ps", "pdf", "xml", "html", ] # Mailing list info URL for lists hosted on the IETF servers MAILING_LIST_INFO_URL = "https://www.ietf.org/mailman/listinfo/%(list_addr)s" diff --git a/ietf/static/css/document_html.scss b/ietf/static/css/document_html.scss new file mode 100644 index 000000000..0d6382276 --- /dev/null +++ b/ietf/static/css/document_html.scss @@ -0,0 +1,316 @@ +@use "sass:map"; + +// FIXME: It's not clear why these three variables remain unset by bs5, but just +// set them to placeholder values so the CSS embedded in the HTML validates. +$btn-font-family: inherit !default; +$nav-link-font-weight: inherit !default; +$tooltip-margin: inherit !default; + +@import "bootstrap/scss/functions"; +@import "bootstrap/scss/variables"; +@import "bootstrap/scss/maps"; +@import "bootstrap/scss/mixins"; +@import "bootstrap/scss/utilities"; +@import "bootstrap/scss/root"; + +// Layout & components +@import "bootstrap/scss/reboot"; +@import "bootstrap/scss/type"; +// @import "bootstrap/scss/images"; +@import "bootstrap/scss/containers"; +@import "bootstrap/scss/grid"; +// @import "bootstrap/scss/tables"; +@import "bootstrap/scss/forms"; +@import "bootstrap/scss/buttons"; +@import "bootstrap/scss/transitions"; +// @import "bootstrap/scss/dropdown"; +@import "bootstrap/scss/button-group"; +@import "bootstrap/scss/nav"; +@import "bootstrap/scss/navbar"; +// @import "bootstrap/scss/card"; +// @import "bootstrap/scss/accordion"; +// @import "bootstrap/scss/breadcrumb"; +@import "bootstrap/scss/pagination"; +@import "bootstrap/scss/badge"; +@import "bootstrap/scss/alert"; +// @import "bootstrap/scss/progress"; +// @import "bootstrap/scss/list-group"; +// @import "bootstrap/scss/close"; +// @import "bootstrap/scss/toasts"; +// @import "bootstrap/scss/modal"; +@import "bootstrap/scss/tooltip"; +// @import "bootstrap/scss/popover"; +// @import "bootstrap/scss/carousel"; +// @import "bootstrap/scss/spinners"; +// @import "bootstrap/scss/offcanvas"; +// @import "bootstrap/scss/placeholders"; + +// Helpers +@import "bootstrap/scss/helpers"; + +// Utilities +@import "bootstrap/scss/utilities/api"; + +:root { + --doc-ptsize-max: 16pt; +} + +.overscroll-none { + overscroll-behavior: none; +} + +.no-scrollbar { + scrollbar-width: none; +} + +.sidebar-toggle[aria-expanded="true"] { + display: none; +} + +.sidebar-toggle[aria-expanded="false"] { + display: inherit; +} + +@media screen { + @include media-breakpoint-down(md) { + body { + padding-top: 60px; + } + + html { + scroll-padding-top: 60px; + } + } + + @include media-breakpoint-down(sm) { + body { + padding-top: 70px; + } + + html { + scroll-padding-top: 70px; + } + } +} + +.rfcmarkup, +.rfchtml { + font-family: var(--bs-font-monospace); + + caption { + padding: 0; + color: var(--bs-body-color); + } + + code { + font-size: 1em; + color: inherit; + } + + @media screen { + @include media-breakpoint-only(xs) { + font-size: min(7pt, var(--doc-ptsize-max)); + } + + @include media-breakpoint-up(sm) { + font-size: min(9.5pt, var(--doc-ptsize-max)); + } + + @include media-breakpoint-up(md) { + font-size: min(9.5pt, var(--doc-ptsize-max)); + } + + @include media-breakpoint-up(lg) { + font-size: min(11pt, var(--doc-ptsize-max)); + } + + @include media-breakpoint-up(xl) { + font-size: min(13pt, var(--doc-ptsize-max)); + } + + @include media-breakpoint-up(xxl) { + font-size: min(16pt, var(--doc-ptsize-max)); + } + + .grey, + hr { + opacity: $hr-opacity; + } + } + + h1, + h2, + h3, + h4, + h5, + h6 { + font-weight: bold; + font-size: 1em; + } + + pre, + code { + font-size: 1em; + } + + pre { + width: 72ch; + margin: 0; + padding: 0; + } + + .bcp14 { + font-weight: bold; + // color: $gray-700; + } +} + +.rfcmarkup { + + h1, + h2, + h3, + h4, + h5, + h6 { + white-space: pre; + display: inline; + } +} + +tbody.meta tr { + + td:first-child, + th:first-child, + td.edit { + display: none; + } +} + +.sidebar { + height: 100vh; + + .toplink, + #name-table-of-contents { + display: none; + } + + th, + td { + display: block; + padding: 0; + } + + td { + margin-bottom: map.get($spacers, 3); + } +} + +// Add some padding when there are multiple buttons in a line that can wrap +.buttonlist .btn { + margin-bottom: map.get($spacers, 1); +} + +// Make revision numbers pagination items fixed-width +.revision-list { + .page-item { + width: 2.2rem; + } + + .page-item.rfc { + width: 6.6rem; + } +} + +#docinfo { + max-height: 70vh; + z-index: -1; +} + + +.badge-obs { + color: white; + background-color: $orange-800; +} + +.badge-ps { + color: black; + background-color: $blue-300; +} + +.badge-exp { + color: black; + background-color: $yellow-200; +} + +.badge-inf { + color: white; + background-color: $orange; +} + +.badge-ds { + color: black; + background-color: $cyan-200; +} + +.badge-hist { + color: white; + background-color: $gray-700; +} + +.badge-std { + color: black; + background-color: $teal-200; +} + +.badge-bcp { + color: white; + background-color: $pink-500; +} + +.badge-unkn { + color: black; + background-color: $gray-300; +} + +.badge-draft { + color: white; + background-color: $danger; +} + +#toc-nav { + width: inherit; + overscroll-behavior-y: none; // Prevent overscrolling from scrolling the main content +} + + +@media print { + @page { + size: letter; + margin: .75in; + } + + body { + margin: 0; + padding: 0; + font-size: 10pt; + } + + pre { + page-break-inside: avoid; + } + + a:link, + a:visited { + // color: inherit; + // text-decoration: none; + } + + .newpage { + page-break-before: always !important; + } + + .noprint { + display: none; + } +} diff --git a/ietf/static/css/document_html_inline.scss b/ietf/static/css/document_html_inline.scss new file mode 100644 index 000000000..a75753438 --- /dev/null +++ b/ietf/static/css/document_html_inline.scss @@ -0,0 +1,6 @@ +@import "document_html"; + +// Make the bootstrap icons available via data-url. +$bootstrap-icons-font-src: url(data-url:npm:bootstrap-icons/font/fonts/bootstrap-icons.woff2) format("woff2"), +url(data-url:npm:bootstrap-icons/font/fonts/bootstrap-icons.woff) format("woff"); +@import "bootstrap-icons/font/bootstrap-icons"; diff --git a/ietf/static/css/document_html_referenced.scss b/ietf/static/css/document_html_referenced.scss new file mode 100644 index 000000000..534298940 --- /dev/null +++ b/ietf/static/css/document_html_referenced.scss @@ -0,0 +1,6 @@ +@import "document_html"; + +// Make the bootstrap icons available. +$bootstrap-icons-font-src: url("npm:bootstrap-icons/font/fonts/bootstrap-icons.woff2") format("woff2"), +url("npm:bootstrap-icons/font/fonts/bootstrap-icons.woff") format("woff"); +@import "bootstrap-icons/font/bootstrap-icons"; diff --git a/ietf/static/css/document_html_txt.scss b/ietf/static/css/document_html_txt.scss new file mode 100644 index 000000000..1a525684c --- /dev/null +++ b/ietf/static/css/document_html_txt.scss @@ -0,0 +1,381 @@ +:root { + --line: 1.2em; + --block: 0 0 0 3ch; + --paragraph: var(--line) 0 var(--line) 3ch; +} + +// WeasyPrint can't handle CSS variables in multi-attribute properties, so work +// around that +// https://github.com/Kozea/WeasyPrint/issues/1219 + +@mixin margin-paragraph { + margin-top: var(--line); + margin-right: 0; + margin-bottom: var(--line); + margin-left: 3ch; +} + +@mixin margin-line { + margin-top: var(--line); + margin-right: 0; + margin-bottom: var(--line); + margin-left: 0; +} + +@mixin margin-block { + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + margin-left: 3ch; +} + +.rfchtml { +// body { +// color: black; +// font-family: monospace; +// font-size: 1em; + line-height: var(--line); + width: 72ch; + + // margin: var(--line) 3ch; + margin-top: var(--line); + margin-right: 3ch; + margin-bottom: var(--line); + margin-left: 3ch; + +h1, h2, h3, h4, h5 { + font-weight: bold; + font-size: inherit; + line-height: inherit; + // margin: var(--line) 0; + @include margin-line; +} +.section-number { + margin-right: 1ch; +} +p { + // margin: var(--paragraph); + @include margin-paragraph; +} +aside, ol, dl { + // margin: var(--block); + @include margin-block; +} +figure { + margin: 0; +} +blockquote { + // margin: var(--paragraph); + @include margin-paragraph; + border-left: 2px solid darkgrey; +} + +// Don't wrap boilerplate URLs; makes it look more like text version. +:is(#status-of-memo, #copyright) a[href] { + white-space: nowrap; +} + +/* Header junk */ +#external-metadata { + display: none !important; /* metadata.min.js is evil because it produces unstyleable goop */ +} +#identifiers dt:is(.label-stream, .label-rfc, .label-std, .label-bcp, .label-internet-draft, + .label-workgroup, .label-intended-status, .label-obsoletes, .label-updates, + .label-published, .label-expires, .label-category, .label-issn, + .label-authors), .ears { + display: none; +} +#identifiers { + margin: 0; + display: grid; + grid-template-columns: 47ch 24ch; + grid-auto-rows: auto; + gap: 0 1ch; +} +#identifiers dt { + margin: 0 1ch 0 0; + float: revert; + display: inline-block; +} +#identifiers dd { + grid-column: 1; + margin: 0; + width: 47ch; + /* HAXX: this gets around the lack of text-content-trim support */ + display: flex; + flex-wrap: wrap; +} +#identifiers dd::before { + margin: 0 1ch 0 0; +} +#identifiers dd.rfc::before { + content: "Request for Comments:"; +} +#identifiers dd.std::before { + content: "STD:"; +} +#identifiers dd.bcp::before { + content: "BCP:"; +} +#identifiers dd.internet-draft::before { + content: "Internet-Draft:"; +} +#identifiers dd.workgroup::before { + content: "Workgroup:"; +} +#identifiers dd.intended-status::before { + content: "Intended Status:"; +} +#identifiers dd.obsoletes::before { + content: "Obsoletes:"; + margin: 0 0 0 -10ch; +} +#identifiers dd.obsoletes { + padding-left: 10ch; + width: 37ch; +} +#identifiers dd.updates::before { + content: "Updates:"; + margin: 0 0 0 -8ch; +} +#identifiers dd.updates { + padding-left: 8ch; + width: 39ch; +} +#identifiers dd:is(.updates, .obsoletes) a { + margin: 0 0 0 1ch; +} +#identifiers dd:is(.updates, .obsoletes) a:last-of-type { + margin: 0 1ch; +} +#identifiers dd.published::before { + content: "Published:"; +} +#identifiers dd.expires::before { + content: "Expires:"; +} +#identifiers dd.category::before { + content: "Category:"; +} +#identifiers dd.issn::before { + content: "ISSN:"; +} +#identifiers dd.authors { + grid-area: 1 / 2 / 100 / 3; + width: 24ch; + text-align: right; + display: block; +} + +#title { + clear: left; + text-align: center; + margin-top: 2em; +} +#rfcnum { + display: none; +} +.toplink { + display: none; +} +nav.toc ul { + list-style: none; + margin: 0; + padding: 0; +} +nav.toc ul > li { + padding-left: 2ch; +} +nav.toc > ul > li { + padding-left: 3ch; +} +nav.toc ul > li > p { + margin: 0; +} + +/* Lists */ +ol, ul { + padding: 0; +} +ol { + margin: 0 0 0 6ch; /* todo: deal with lists that have >= 10 items */ +} +ol ol, ul ol { + margin: 0 0 0 3ch; +} +ul { + margin: 0 0 0 4ch; + list-style-type: '*'; +} +ul ul { + list-style-type: '-'; +} +ul ul, ol ul { + margin-left: 1ch; +} +ul ul ul { + list-style-type: 'o'; +} +li { + // margin: var(--line) 0; + @include margin-line; + padding: 0 0 0 2ch; +} +.compact li { + margin: 0; +} +li p:first-child, dd p:first-child { + margin: 0; +} +dt { + float: left; + clear: left; + margin: 0 2ch 0 0; + break-after: avoid; +} +dd { + // margin: var(--paragraph); + @include margin-paragraph; + break-before: avoid; +} +dl.compact dt, dl.compact dd { + margin-top: 0; + margin-bottom: 0; +} +dl.references dt { + margin-right: 1ch; +} +dl.references dd { + margin-left: 11ch; +} +dd.break { + display: none; +} + +/* Figures, tables */ +pre { + // margin: var(--line) 0; + @include margin-line; +} +div.artwork, div.sourcecode { + display: flex; + flex-wrap: nowrap; + align-items: end; +} +div.artwork.alignCenter, div.sourcecode.alignCenter { + justify-content: center; +} +div.artwork.alignRight, div.sourcecode.alignRight { + justify-content: end; +} +div.artwork::before, div.sourcecode::before { + flex: 0 1 3ch; + content: ""; +} +div.artwork.alignRight::before, div.sourcecode.alignRight::before { + flex-grow: 1; +} +div.artwork pre, div.sourcecode pre { + flex: 0 0 content; + margin: 0; + max-width: 72ch; + overflow: auto; +} +div.artwork .pilcrow, div.sourcecode .pilcrow { + flex: 0 0 1ch; +} +figcaption, table caption { + text-align: center; + margin-top: var(--line); +} +table { + --half-line: calc(var(--line) / 2 - 1px); + caption-side: bottom; + // margin: var(--line) 0 var(--half-line) 3ch; + margin-top: var(--line); + margin-right: 0; + margin-bottom: var(--half-line); + margin-left: 3ch; + border-collapse: collapse; +} +table.center { + margin-left: auto; /* todo: add 3ch */ + margin-right: auto; +} +table caption { + margin-top: calc(var(--half-line) + var(--line)); +} +thead, tfoot { + border-top-style: double; + border-bottom-style: double; +} +td, th { + border: 1px solid black; + // padding: var(--half-line) 1ch; + padding-top: var(--half-line); + padding-right: 1ch; + padding-bottom: var(--half-line); + padding-left: 1ch; +} +.text-left { + text-align: left; +} +.text-center { + text-align: center; +} +.text-right { + text-align: right; +} + +/* Links */ +a.selfRef, a.pilcrow { + color: black; + text-decoration: none; +} +a.relref, a.xref { + hyphens: none; +} +a.relref, a.xref.cite { + white-space: nowrap; +} +.pilcrow { + display: inline-block; + margin-right: -1ch; + opacity: 0.01; + text-decoration: none; +} +:hover > .pilcrow { + opacity: 0.2; +} +* > .pilcrow[href]:hover { + opacity: 0.6; +} + +/* sup, sub */ +sup, sub { + line-height: 1.1; +} + +/* Authors */ +address { + font-style: normal; + // margin: 2em 0 var(--line) 3ch; + margin-top: 2em; + margin-right: 0; + margin-bottom: var(--line); + margin-left: 3ch; +} +h2 + address { + margin-top: 1em; +} +address .tel, address .email { + // margin: var(--line) 0 0; + margin-top: var(--line); + margin-right: 0; + margin-bottom: 0; + margin-left: 0; +} +address .tel + .email { + margin: 0; +} +} diff --git a/ietf/static/css/ietf.scss b/ietf/static/css/ietf.scss index 86b71f154..9cfb610b7 100644 --- a/ietf/static/css/ietf.scss +++ b/ietf/static/css/ietf.scss @@ -168,8 +168,9 @@ table tbody.meta { } } -// Try and hyphenate table headings -th { +// Try and hyphenate table headings and other things +th, +.hyphenate { hyphens: auto; } @@ -208,6 +209,17 @@ th { max-width: 300px; } +// Make revision numbers pagination items fixed-width +.revision-list { + .page-item { + width: 2.2rem; + } + + .page-item.rfc { + width: 6.6rem; + } +} + // Style the photo cards .photo { width: 12em; diff --git a/ietf/static/css/select2.scss b/ietf/static/css/select2.scss index 4fee939bd..44824a358 100644 --- a/ietf/static/css/select2.scss +++ b/ietf/static/css/select2.scss @@ -3,21 +3,3 @@ @import "bootstrap/scss/mixins"; @import "select2/src/scss/core"; @import "select2-bootstrap-5-theme/src/include-all"; - -// Propagate readonly property from input to select2 instrumentation, based on -// https://stackoverflow.com/questions/41807096/select2-make-it-readonly-not-disabled-from-js/55001516#55001516 -select[readonly].select2-hidden-accessible + .select2-container { - pointer-events: none; - touch-action: none; - - .select2-selection { - background: $form-select-disabled-bg; - color: $form-select-disabled-color; - border-color: $form-select-disabled-border-color; - box-shadow: none; - } - - .select2-selection__arrow, .select2-selection__clear { - display: none; - } -} diff --git a/ietf/static/images/irtf-logo.svg b/ietf/static/images/irtf-logo.svg index ad339fe2b..be64890b2 100644 --- a/ietf/static/images/irtf-logo.svg +++ b/ietf/static/images/irtf-logo.svg @@ -1 +1,76 @@ -R \ No newline at end of file + + + + + + R + + + + + + + diff --git a/ietf/static/js/document_html.js b/ietf/static/js/document_html.js new file mode 100644 index 000000000..077ef6ece --- /dev/null +++ b/ietf/static/js/document_html.js @@ -0,0 +1,71 @@ +import { + Tooltip as Tooltip, + // Button as Button, + // Collapse as Collapse, + // ScrollSpy as ScrollSpy, + Tab as Tab +} from "bootstrap"; + +import Cookies from "js-cookie"; +import { populate_nav } from "./nav.js"; + +const cookies = Cookies.withAttributes({ sameSite: "strict" }); + +document.addEventListener("DOMContentLoaded", function (event) { + // handle point size slider + const cookie = "doc-ptsize-max"; + + function change_ptsize(ptsize) { + document.documentElement.style.setProperty(`--${cookie}`, + `${ptsize}pt`); + cookies.set(cookie, ptsize); + } + + document.getElementById("ptsize") + .oninput = function () { change_ptsize(this.value) }; + + const ptsize = cookies.get(cookie); + change_ptsize(ptsize ? Math.min(Math.max(7, ptsize), 16) : 12); + + // Use the Bootstrap tooltip plugin for all elements with a title attribute + const tt_triggers = document.querySelectorAll( + "[title]:not([title=''])"); + [...tt_triggers].map(tt_el => { + const tooltip = Tooltip.getOrCreateInstance(tt_el); + tt_el.addEventListener("click", el => { + tooltip.hide(); + tt_el.blur(); + }); + }); + + // Set up a nav pane + const toc_pane = document.getElementById("toc-nav"); + populate_nav(toc_pane, + `#content h2, #content h3, #content h4, #content h5, #content h6 + #content .h1, #content .h2, #content .h3, #content .h4, #content .h5, #content .h6`, + ["py-0"]); + + // activate pref buttons selected by pref cookies + document.querySelectorAll(".btn-check") + .forEach(btn => { + const id = btn.id.replace("-radio", ""); + if (cookies.get(btn.name) == id) { + btn.checked = true; + } + btn.addEventListener("click", el => { + cookies.set(btn.name, id); + window.location.reload(); + }); + }); + + // activate tab selected in prefs + let defpane; + try { + defpane = Tab.getOrCreateInstance( + `#${cookies.get("deftab")}-tab`); + } catch (err) { + defpane = Tab.getOrCreateInstance("#docinfo-tab"); + }; + defpane.show(); + document.activeElement.blur(); +}); diff --git a/ietf/static/js/document_relations.js b/ietf/static/js/document_relations.js index 013b85259..07a85d0da 100644 --- a/ietf/static/js/document_relations.js +++ b/ietf/static/js/document_relations.js @@ -315,7 +315,7 @@ function draw_graph(data, group) { }) ]; - // // See https://github.com/d3/d3-force/blob/master/README.md#simulation_tick + // // See https://github.com/d3/d3-force/blob/main/README.md#simulation_tick // for (let i = 0, n = Math.ceil(Math.log(simulation.alphaMin()) / // Math.log(1 - simulation.alphaDecay())); i < // n; ++i) { diff --git a/ietf/static/js/ietf.js b/ietf/static/js/ietf.js index c30fe6fcb..17165bbfb 100644 --- a/ietf/static/js/ietf.js +++ b/ietf/static/js/ietf.js @@ -24,7 +24,7 @@ if (!process.env.BUILD_DEPLOY) { import Cookies from "js-cookie"; -import debounce from "lodash/debounce"; +import { populate_nav } from "./nav.js"; // setup CSRF protection using jQuery function csrfSafeMethod(method) { @@ -154,9 +154,9 @@ $(document) $(function () { const contentElement = $('#content.ietf-auto-nav'); if (contentElement.length > 0) { + const heading_selector = "h2:not([style='display:none']):not(.navskip), h3:not([style='display:none']):not(.navskip), h4:not([style='display:none']):not(.navskip), h5:not([style='display:none']):not(.navskip), h6:not([style='display:none']):not(.navskip), .nav-heading:not([style='display:none']):not(.navskip)"; const headings = contentElement - .find("h1:visible, h2:visible, h3:visible, h4:visible, h5:visible, h6:visible, .nav-heading:visible") - .not(".navskip"); + .find(heading_selector); const contents = (headings.length > 0) && ($(headings) @@ -178,8 +178,6 @@ $(function () { if (pageTooTall || haveExtraNav) { // console.log("Enabling nav."); - let n = 0; - let last_level; contentElement .attr("data-bs-offset", 0) @@ -197,48 +195,7 @@ $(function () { .children() .last(); - contentElement - .find("h1:visible, h2:visible, h3:visible, h4:visible, h5:visible, h6:visible, .nav-heading:visible") - .not(".navskip") - .each(function () { - // Some headings have line breaks in them - only use first line in that case. - const frag = $(this) - .html() - .split(" $(x) - .text()) - .join(" "); - - if (text === undefined || text === "") { - // Nothing to do for empty headings. - return; - } - let id = $(this) - .attr("id"); - - if (id === undefined) { - id = `autoid-${++n}`; - $(this) - .attr("id", id); - } - - const level = parseInt(this.nodeName.substring(1)) - 1; - if (!last_level) { - last_level = level; - } - - if (level > last_level) { - last_level = level; - } else - while (level < last_level) { - last_level--; - } - - $(nav) - .append(`${text}`); - }); + populate_nav(nav[0], heading_selector); if (haveExtraNav) { $('#righthand-panel').append('
'); @@ -246,18 +203,6 @@ $(function () { extraNav.remove(); } - $(document) - // Chrome apparently wants this debounced to something >10ms, - // otherwise the main view doesn't scroll? - .on("scroll", debounce(function () { - const item = $('#righthand-nav') - .find(".active") - .last(); - if (item.length) { - item[0].scrollIntoView({ block: "center", behavior: "smooth" }); - } - }, 100)); - // offset the scrollspy to account for the menu bar const contentOffset = contentElement ? contentElement.offset().top : 0; diff --git a/ietf/static/js/nav.js b/ietf/static/js/nav.js new file mode 100644 index 000000000..f89e8466b --- /dev/null +++ b/ietf/static/js/nav.js @@ -0,0 +1,99 @@ +import debounce from "lodash/debounce"; + +function make_nav() { + const nav = document.createElement("nav"); + nav.classList.add("nav-pills", "ps-3", "flex-column"); + return nav; +} + +function get_level(el) { + let h; + if (el.tagName.match(/^h\d/i)) { + h = el.tagName + } else { + el.classList.forEach(cl => { + if (cl.match(/^h\d/i)) { + h = cl; + return; + } + }); + } + return h.charAt(h.length - 1); +} + +export function populate_nav(nav, heading_selector, classes) { + // Extract section headings from document + const headings = document.querySelectorAll(heading_selector); + const min_level = Math.min(...Array.from(headings) + .map(get_level)); + + let nav_stack = [nav]; + let cur_level = 0; + let n = 0; + + headings.forEach(el => { + const level = get_level(el) - min_level; + + if (level < cur_level) { + while (level < cur_level) { + let nav = nav_stack.pop(); + cur_level--; + nav_stack[level].appendChild(nav); + } + } else { + while (level > cur_level) { + nav_stack.push(make_nav()); + cur_level++; + } + } + + const link = document.createElement("a"); + link.classList.add("nav-link", "ps-1", "d-flex", "hyphenate", + classes); + + if (!el.id) { + el.id = `autoid-${++n}`; + } + link.href = `#${el.id}`; + + const words = el.innerText.split(/\s+/); + let nr = ""; + if (words[0].includes(".")) { + nr = words.shift(); + } else if (words.length > 1 && words[1].includes(".")) { + nr = words.shift() + " " + words.shift(); + nr = nr.replace(/\s*Appendix\s*/, ""); + } + + if (nr) { + const number = document.createElement("div"); + number.classList.add("pe-1"); + number.textContent = nr; + link.appendChild(number); + } + + const text = document.createElement("div"); + text.classList.add("text-break"); + text.textContent = words.join(" "); + link.appendChild(text); + + nav_stack[level].appendChild(link); + }); + + for (var i = nav_stack.length - 1; i > 0; i--) { + nav_stack[i - 1].appendChild(nav_stack[i]); + } + + // Chrome apparently wants this debounced to something >10ms, + // otherwise the main view doesn't scroll? + document.addEventListener("scroll", debounce(function () { + const items = nav.querySelectorAll(".active"); + const item = [...items].pop(); + if (item) { + item.scrollIntoView({ + block: "center", + behavior: "smooth" + }); + } + }, 100)); +} diff --git a/ietf/stats/views.py b/ietf/stats/views.py index 09a25b47b..dd1cc96bd 100644 --- a/ietf/stats/views.py +++ b/ietf/stats/views.py @@ -345,7 +345,7 @@ def document_stats(request, stats_type=None): basename, ext = t ext = ext.lower() - if not any(ext==whitelisted_ext for whitelisted_ext in settings.DOCUMENT_FORMAT_WHITELIST): + if not any(ext==allowlisted_ext for allowlisted_ext in settings.DOCUMENT_FORMAT_ALLOWLIST): continue canonical_name = doc_names_with_missing_types.get(basename) diff --git a/ietf/submit/forms.py b/ietf/submit/forms.py index 79ecf46f6..7a4bd38f1 100644 --- a/ietf/submit/forms.py +++ b/ietf/submit/forms.py @@ -338,7 +338,7 @@ class SubmissionBaseUploadForm(forms.Form): if group: return group else: - raise forms.ValidationError('Draft names starting with draft-%s- are restricted, please pick a differen name' % ntype) + raise forms.ValidationError('Draft names starting with draft-%s- are restricted, please pick a different name' % ntype) return None diff --git a/ietf/submit/test_submission.txt b/ietf/submit/test_submission.txt index 37a433ef8..5e828e53f 100644 --- a/ietf/submit/test_submission.txt +++ b/ietf/submit/test_submission.txt @@ -2,7 +2,7 @@ -Network Working Group %(initials)s %(surname)s +Network Working Group %(firstpagename)37s Internet-Draft Test Centre Inc. Intended status: Informational %(month)s %(year)s Expires: %(expiration)s @@ -10,7 +10,6 @@ Expires: %(expiration)s %(title)s %(name)s - Abstract This document describes how to test tests. diff --git a/ietf/submit/tests.py b/ietf/submit/tests.py index 03606b23f..ad1891c95 100644 --- a/ietf/submit/tests.py +++ b/ietf/submit/tests.py @@ -107,6 +107,10 @@ def submission_file(name_in_doc, name_in_post, group, templatename, author=None, if year is None: year = _today.strftime("%Y") + # extract_authors() cuts the author line off at the first space past 80 characters + # very long factory-generated names can hence be truncated, causing a failure + # ietf/submit/test_submission.txt was changed so that 37-character names and shorter will work + # this may need further adjustment if longer names still cause failures submission_text = template % dict( date=_today.strftime("%d %B %Y"), expiration=(_today + datetime.timedelta(days=100)).strftime("%d %B, %Y"), @@ -119,6 +123,7 @@ def submission_file(name_in_doc, name_in_post, group, templatename, author=None, asciiAuthor=author.ascii, initials=author.initials(), surname=author.ascii_parts()[3] if ascii else author.name_parts()[3], + firstpagename=f"{author.initials()} {author.ascii_parts()[3] if ascii else author.name_parts()[3]}", asciiSurname=author.ascii_parts()[3], email=email, title=title, diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py index 1da11f01e..1fde4fa1d 100644 --- a/ietf/sync/rfceditor.py +++ b/ietf/sync/rfceditor.py @@ -567,7 +567,7 @@ def post_approved_draft(url, name): "Authorization": "Basic %s" % force_str(base64.encodebytes(smart_bytes("%s:%s" % (username, password)))).replace("\n", ""), } - log("Posting RFC-Editor notifcation of approved draft '%s' to '%s'" % (name, url)) + log("Posting RFC-Editor notification of approved draft '%s' to '%s'" % (name, url)) text = error = "" try: diff --git a/ietf/templates/base/menu_user.html b/ietf/templates/base/menu_user.html index addab232f..bb68855b6 100644 --- a/ietf/templates/base/menu_user.html +++ b/ietf/templates/base/menu_user.html @@ -186,8 +186,8 @@
  • - Account whitelist + href="{% url 'ietf.ietfauth.views.add_account_allowlist' %}"> + Account allowlist
  • {% endif %} diff --git a/ietf/templates/debug.html b/ietf/templates/debug.html index 3eea27c19..f7788e776 100644 --- a/ietf/templates/debug.html +++ b/ietf/templates/debug.html @@ -2,7 +2,7 @@ {% load origin %} {% load static %} {% origin %} -{% if debug %} +{% if settings.DEBUG %} {% if sql_debug %} {% load debug_filters %} {{ sql_queries|length }} queries ({{ sql_queries|timesum }}s) diff --git a/ietf/templates/doc/document_draft.html b/ietf/templates/doc/document_draft.html index 0bb597b60..f763159fc 100644 --- a/ietf/templates/doc/document_draft.html +++ b/ietf/templates/doc/document_draft.html @@ -26,7 +26,7 @@ {% block content %} {% origin %} {{ top|safe }} - {% include "doc/revisions_list.html" %} + {% include "doc/revisions_list.html" with document_html=document_html %}
    {% if doc.rev != latest_rev %}
    The information below is for an old version of the document.
    @@ -38,363 +38,7 @@ {% endif %} {% endif %} - - - - - - - - - - - - - - - - - - - - {% if replaces or can_edit_stream_info %} - - - - - - - {% endif %} - {% if replaced_by %} - - - - - - - {% endif %} - {% if can_view_possibly_replaces %} - {% if possibly_replaces %} - - - - - - - {% endif %} - {% if possibly_replaced_by %} - - - - - - - {% endif %} - {% endif %} - - - - - - - {% if doc.get_state_slug != "rfc" and not snapshot %} - - - - - - - {% endif %} - - - - - - - {% for check in doc.submission.latest_checks %} - {% if check.passed != None and check.symbol.strip %} - - - - - - - {% endif %} - {% endfor %} - {% if review_assignments or can_request_review %} - - - - - - - {% endif %} - {% if conflict_reviews %} - - - - - - - {% endif %} - {% with doc.docextresource_set.all as resources %} - {% if resources or can_edit_stream_info or can_edit_individual %} - - - - - - - {% endif %} - {% endwith %} - + {% include "doc/document_info.html" %} + + + + + + + {% if document_html %} + + + + + + + {% if doc.rev != "00" %} + + + + + + + {% endif %} + {% endif %} + + + + + + + {% if not document_html %} + {# FIXME: This shows the date of the last history event, which is not what participants necessarily expect here. #} + + + + + + + {% endif %} + {% if replaces or not document_html and can_edit_stream_info %} + + + + + + + {% endif %} + {% if replaced_by %} + + + + + + + {% endif %} + {% if can_view_possibly_replaces %} + {% if possibly_replaces %} + + + + + + + {% endif %} + {% if possibly_replaced_by %} + + + + + + + {% endif %} + {% endif %} + + + + + + + {% if doc.get_state_slug != "rfc" and not snapshot %} + + + + + + + {% endif %} + + + + + + + {% for check in doc.submission.latest_checks %} + {% if check.passed != None and check.symbol.strip %} + + + + + + + {% endif %} + {% endfor %} + {% if not document_html %} + {% if review_assignments or can_request_review %} + + + + + + + {% endif %} + {% if conflict_reviews %} + + + + + + + {% endif %} + {% endif %} + {% with doc.docextresource_set.all as resources %} + {% if document_html and resources or document_html and doc.group and doc.group.list_archive %} + {% if resources or doc.group and doc.group.list_archive or can_edit_stream_info or can_edit_individual %} + + + + + + + {% endif %} + {% endif %} + {% endwith %} + \ No newline at end of file diff --git a/ietf/templates/doc/htmlized_base.html b/ietf/templates/doc/htmlized_base.html deleted file mode 100644 index e3624c0df..000000000 --- a/ietf/templates/doc/htmlized_base.html +++ /dev/null @@ -1,139 +0,0 @@ - -{% load ietf_filters static %} -{# Copyright The IETF Trust 2021, All Rights Reserved #} -{% load origin %} -{% origin %} - - - - - - {% block title %}No title{% endblock %} - - - - {% block pagehead %}{% endblock %} - {% include "base/icons.html" %} - - -
    - {% block content %}{{ content|safe }}{% endblock %} - {% block content_end %}{% endblock %} -
    - {% block footer %}{% endblock %} - {% block js %}{% endblock %} - - \ No newline at end of file diff --git a/ietf/templates/doc/mail/resurrect_completed_email.txt b/ietf/templates/doc/mail/resurrect_completed_email.txt index 7b5e52be5..8bff58540 100644 --- a/ietf/templates/doc/mail/resurrect_completed_email.txt +++ b/ietf/templates/doc/mail/resurrect_completed_email.txt @@ -1,4 +1,4 @@ -{% autoescape off %}As you requsted, the Internet Draft {{ doc.file_tag }} +{% autoescape off %}As you requested, the Internet Draft {{ doc.file_tag }} has been resurrected. Datatracker URL: {{ url }} diff --git a/ietf/templates/doc/revisions_list.html b/ietf/templates/doc/revisions_list.html index 04b12ed09..578263447 100644 --- a/ietf/templates/doc/revisions_list.html +++ b/ietf/templates/doc/revisions_list.html @@ -1,21 +1,38 @@ {# Copyright The IETF Trust 2015, All Rights Reserved #} {% load origin %} {% origin %} - - +{% endif %} \ No newline at end of file diff --git a/ietf/templates/ietfauth/whitelist_form.html b/ietf/templates/ietfauth/allowlist_form.html similarity index 91% rename from ietf/templates/ietfauth/whitelist_form.html rename to ietf/templates/ietfauth/allowlist_form.html index 8b5670931..92d40e53e 100644 --- a/ietf/templates/ietfauth/whitelist_form.html +++ b/ietf/templates/ietfauth/allowlist_form.html @@ -6,18 +6,18 @@ {% block content %} {% origin %} {% if success %} -

    Whitelist entry creation successful

    +

    Allowlist entry creation successful

    Please ask the requestor to try the account creation form - again, with the whitelisted email address. + again, with the allowlisted email address.

    {% else %} -

    Add a whitelist entry for account creation.

    +

    Add an allowlist entry for account creation.

    When an email request comes in for assistance with account creation because the automated account creation has failed, you can add the - address to an account creation whitelist here. + address to an account creation allowlist here.

    Before you do so, please complete the following 3 verification steps: @@ -35,7 +35,7 @@

  • Google for the person's name within the ietf.org site: "Jane Doe site:ietf.org". If found, and the email address matches an address used in drafts or discussions, - things are fine, and it's OK to add the address to the whitelist using this form, + things are fine, and it's OK to add the address to the allowlist using this form, and ask the person to please try the account creation form again. @@ -62,7 +62,7 @@ question as given above, with lowercase "wgs".

    - If the answer to this question shows clue, then add the address to the whitelist + If the answer to this question shows clue, then add the address to the allowlist using this form, and ask the person to please try the account creation form again. @@ -72,7 +72,7 @@
    {% csrf_token %} {% bootstrap_form form %} - + {% endif %} {% endblock %} \ No newline at end of file diff --git a/ietf/templates/meeting/add_session_drafts.html b/ietf/templates/meeting/add_session_drafts.html index 1efcc5963..92c33ed4a 100644 --- a/ietf/templates/meeting/add_session_drafts.html +++ b/ietf/templates/meeting/add_session_drafts.html @@ -22,7 +22,7 @@

    This form will link additional drafts to this session with a revision of "Current at time of presentation". For more fine grained control of versions, or to remove a draft from a session, adjust the sessions associated with a draft from the draft's main page.
    -

    Drafts already linked to this sesssion

    +

    Drafts already linked to this session

  • DocumentType - {% if doc.get_state_slug == "rfc" and not snapshot %} - RFC - {{ doc.std_level }} - {% if published %} - ({{ doc.pub_date|date:"F Y" }}) - {% else %} - (Publication date unknown) - {% endif %} - {% if has_verified_errata %} - - Errata - - {% elif has_errata %} - - Errata - - {% endif %} - {% if obsoleted_by %}
    Obsoleted by {{ obsoleted_by|urlize_related_source_list|join:", " }}
    {% endif %} - {% if updated_by %}
    Updated by {{ updated_by|urlize_related_source_list|join:", " }}
    {% endif %} - {% if obsoletes %}
    Obsoletes {{ obsoletes|urlize_related_target_list|join:", " }}
    {% endif %} - {% if updates %}
    Updates {{ updates|urlize_related_target_list|join:", " }}
    {% endif %} - {% if status_changes %} -
    Status changed by {{ status_changes|urlize_related_source_list|join:", " }}
    - {% endif %} - {% if proposed_status_changes %} -
    Proposed status changed by {{ proposed_status_changes|urlize_related_source_list|join:", " }}
    - {% endif %} - {% if rfc_aliases %}
    Also known as {{ rfc_aliases|join:", "|urlize_ietf_docs }}
    {% endif %} - {% if draft_name %} -
    - Was - {{ draft_name }} - {% if submission %}({{ submission|safe }}){% endif %} -
    - {% endif %} - {% else %} - {% if snapshot and doc.doc.get_state_slug == 'rfc' %} - This is an older version of an Internet-Draft that was ultimately published as an RFC. - {% elif snapshot and doc.rev != latest_rev %} - This is an older version of an Internet-Draft whose latest revision is {{ doc.doc.get_state }} - {% else %} - {% if snapshot and doc.rev == latest_rev %}{{ doc.doc.get_state }}{% else %}{{ doc.get_state }}{% endif %} Internet-Draft - {% if submission %}({{ submission|safe }}){% endif %} - {% if resurrected_by %}- resurrect requested by {{ resurrected_by }}{% endif %} - {% endif %} - {% endif %} -
    Author{{ doc.authors|pluralize }} - {% if can_edit_authors %} - Edit - {% endif %} - - {# Implementation that uses the current primary email for each author #} - {% for author in doc.authors %} - {% person_link author %}{% if not forloop.last %},{% endif %} - {% endfor %} -
    Last updated - {{ doc.time|date:"Y-m-d" }} - {% if latest_revision and latest_revision.time|date:"Y-m-d" != doc.time|date:"Y-m-d" %} - (Latest revision {{ latest_revision.time|date:"Y-m-d" }}) - {% endif %} -
    Replaces - {% if can_edit_stream_info and not snapshot %} - Edit - {% endif %} - - {% if replaces %} - {{ replaces|urlize_related_target_list|join:", " }} - {% else %} - (None) - {% endif %} -
    - Replaced by - - - {{ replaced_by|urlize_related_source_list|join:", " }} -
    - Possibly Replaces - - {% if can_edit_replaces and not snapshot %} - - Edit - - {% endif %} - - {{ possibly_replaces|urlize_related_target_list|join:", " }} -
    - Possibly Replaced By - - {% if can_edit_replaces and not snapshot %} - {% comment %}Edit{% endcomment %} - {% endif %} - - {{ possibly_replaced_by|urlize_related_source_list|join:", " }} -
    - Stream - - {% if can_change_stream and not snapshot %} - - Edit - - {% endif %} - - {{ stream_desc }} -
    - Intended RFC status - - {% if can_edit_stream_info and not snapshot %} - - Edit - - {% endif %} - - {% if doc.intended_std_level %} - {{ doc.intended_std_level }} - {% else %} - - (None) - - {% endif %} -
    - Formats - - - {% if doc.get_state_slug != "active" and doc.get_state_slug != "rfc" %} -
    - Expired & archived -
    - {% endif %} - {% include "doc/document_format_buttons.html" %} -
    - {{ check.checker|title }} - - - {% if check.errors or check.warnings %} - - {{ check.symbol|safe }} - - {% else %} - - {{ check.symbol|safe }} - - {% endif %} - - {{ check.errors }} errors, {{ check.warnings }} warnings - - {% include "doc/yang-check-modal-overlay.html" %} -
    - Reviews - - - {% for review_assignment in review_assignments %} - {% include "doc/review_assignment_summary.html" with current_doc_name=doc.name current_rev=doc.rev %} - {% endfor %} - {% if no_review_from_teams %} - {% for team in no_review_from_teams %} - {{ team.acronym.upper }}{% if not forloop.last %},{% endif %} - {% endfor %} - will not review this version - {% endif %} - {% if can_request_review or can_submit_unsolicited_review_for_teams %} -
    - {% if can_request_review %} - - - - Request review - - {% endif %} - {% if can_submit_unsolicited_review_for_teams|length == 1 %} - - - - Submit unsolicited review - - {% elif can_submit_unsolicited_review_for_teams %} - - - - Submit unsolicited review - - {% endif %} -
    - {% endif %} -
    - IETF conflict review - - - {{ conflict_reviews|join:", "|urlize_ietf_docs }} -
    - - Additional resources - - {% if can_edit_stream_info or can_edit_individual %} - - Edit - - {% endif %} - - {% if resources or doc.group and doc.group.list_archive %} - {% for resource in resources|dictsort:"display_name" %} - {% if resource.name.type.slug == 'url' or resource.name.type.slug == 'email' %} - - {% firstof resource.display_name resource.name.name %} - -
    - {# Maybe make how a resource displays itself a method on the class so templates aren't doing this switching #} - {% else %} - - {% firstof resource.display_name resource.name.name %}: {{ resource.value|escape }} - -
    - {% endif %} - {% endfor %} - {% if doc.group and doc.group.list_archive %} - {% if doc.group.list_archive|startswith:settings.MAILING_LIST_ARCHIVE_URL %} - - Mailing list discussion - - {% elif doc.group.list_archive|is_valid_url %} - - Mailing list discussion - - {% else %} - {{ doc.group.list_archive|urlencode }} - {% endif %} - {% endif %} - {% endif %} -
    diff --git a/ietf/templates/doc/document_format_buttons.html b/ietf/templates/doc/document_format_buttons.html index 1dcf3008c..efd125766 100644 --- a/ietf/templates/doc/document_format_buttons.html +++ b/ietf/templates/doc/document_format_buttons.html @@ -1,7 +1,18 @@ {% if file_urls %} {% else %} diff --git a/ietf/templates/doc/document_html.html b/ietf/templates/doc/document_html.html index 1f0ed51fc..ee090fcfe 100644 --- a/ietf/templates/doc/document_html.html +++ b/ietf/templates/doc/document_html.html @@ -1,92 +1,276 @@ -{% extends "doc/htmlized_base.html" %} {# Copyright The IETF Trust 2016, All Rights Reserved #} + +{% load analytical %} {% load origin %} {% load static %} -{% load ietf_filters %} -{% block pagehead %} - - -{% endblock %} -{% block morecss %} - .bgwhite { background-color: white; } - .bgred { background-color: #F44; } - .bggrey { background-color: #666; } - .bgbrown { background-color: #840; } - .bgorange { background-color: #FA0; } - .bgyellow { background-color: #EE0; } - .bgmagenta{ background-color: #F4F; } - .bgblue { background-color: #66F; } - .bgcyan { background-color: #4DD; } - .bggreen { background-color: #4F4; } - .draftcontent { margin-top:1em;} -{% endblock %} -{% block title %} - {% if doc.get_state_slug == "rfc" %} - RFC {{ doc.rfc_number }} - {{ doc.title }} - {% else %} - {{ doc.name }}-{{ doc.rev }} - {% endif %} -{% endblock %} -{% block content %} - - {% origin %} -
    -
    -
    +{% load ietf_filters textfilters %} +{% origin %} + + + {% analytical_head_top %} + + + + {% if not snapshot and doc.get_state_slug == "rfc" %} + RFC {{ doc.rfc_number }} - {{ doc.title }} + {% else %} + {{ doc.name }}-{{ doc.rev }} + {% endif %} + + + {% if request.COOKIES.pagedeps == 'inline' %} + + + {% else %} + + {% if html %} + + {% endif %} + + {% endif %} + + + {% include "base/icons.html" %} + {% include "doc/opengraph.html" %} + {% analytical_head_bottom %} + + + {% analytical_body_top %} + + +
    +
    + {% if html and request.COOKIES.htmlconf != 'txt' %} +
    +
    + {{ html|safe }} +
    + {% else %} +
    +
    + + {{ doc.htmlized|default:"Generation of htmlized text failed"|linkify|safe }} +
    + {% endif %} +
    +
    - {% if doc.meta %} -
    -
    {{ doc.supermeta|safe }}
    -{{ doc.meta|safe }}
    -
    - {% endif %} -
    {{ doc.htmlized|default:"Generation of htmlized text failed"|safe }}
    -
    -{% endblock %} -{% block js %} - -{% endblock %} \ No newline at end of file + {% analytical_body_bottom %} + + \ No newline at end of file diff --git a/ietf/templates/doc/document_info.html b/ietf/templates/doc/document_info.html new file mode 100644 index 000000000..0548f350b --- /dev/null +++ b/ietf/templates/doc/document_info.html @@ -0,0 +1,449 @@ +{# Copyright The IETF Trust 2016-2020, All Rights Reserved #} +{% load origin %} +{% load static %} +{% load ietf_filters %} +{% load person_filters %} +{% origin %} + +
    Document{% if document_html %}Document type{% else %}Type{% endif %} + {% if doc.get_state_slug == "rfc" and not snapshot %} + RFC + {% if not document_html %} + - {{ doc.std_level }} + {% else %} + {{ doc.std_level }} + {% endif %} + + {% if published %} + {% if document_html %}
    {% else %}({% endif %}{{ doc.pub_date|date:"F Y" }}{% if not document_html %}){% endif %} + {% else %} + (Publication date unknown) + {% endif %} + {% if document_html %}
    {% endif %} + {% if has_verified_errata or has_errata %} + + Errata + + {% endif %} + {% if doc.related_ipr %} + IPR + {% endif %} + {% if obsoleted_by %}
    Obsoleted by {{ obsoleted_by|urlize_related_source_list:document_html|join:", " }}
    {% endif %} + {% if updated_by %}
    Updated by {{ updated_by|urlize_related_source_list:document_html|join:", " }}
    {% endif %} + {% if obsoletes %}
    Obsoletes {{ obsoletes|urlize_related_target_list:document_html|join:", " }}
    {% endif %} + {% if updates %}
    Updates {{ updates|urlize_related_target_list:document_html|join:", " }}
    {% endif %} + {% if status_changes %} +
    Status changed by {{ status_changes|urlize_related_source_list|join:", " }}
    + {% endif %} + {% if proposed_status_changes %} +
    Proposed status changed by {{ proposed_status_changes|urlize_related_source_list|join:", " }}
    + {% endif %} + {% if rfc_aliases %}
    Also known as {{ rfc_aliases|join:", "|urlize_ietf_docs }}
    {% endif %} + {% if draft_name %} +
    + Was + {{ draft_name }} + {% if submission %}({{ submission|safe }}){% endif %} +
    + {% endif %} + {% else %} + {% if snapshot and doc.doc.get_state_slug == 'rfc' %} + This is an older version of an Internet-Draft that was ultimately published as {{doc.doc.canonical_name|prettystdname}}. + {% elif snapshot and doc.rev != latest_rev %} + This is an older version of an Internet-Draft whose latest revision state is "{{ doc.doc.get_state }}". + {% else %} + {% if snapshot and doc.rev == latest_rev %}{{ doc.doc.get_state }}{% else %}{{ doc.get_state }}{% endif %} Internet-Draft + {% if submission %}({{ submission|safe }}){% endif %} + {% if resurrected_by %}- resurrect requested by {{ resurrected_by }}{% endif %} + {% endif %} + {% endif %} + {% if doc.get_state_slug != "active" and doc.get_state_slug != "rfc" %} +
    + Expired & archived +
    + {% endif %} +
    Select version + {% include "doc/revisions_list.html" with document_html=document_html %} +
    Compare versions + Inline + Side-by-side +
    Author{{ doc.authors|pluralize }} + {% if can_edit_authors %} + Edit + {% endif %} + + {# Implementation that uses the current primary email for each author #} + {% for author in doc.authors %} + {% person_link author %}{% if not forloop.last %},{% endif %} + {% endfor %} + {% if document_html and not snapshot or document_html and doc.rev == latest_rev%} +
    + Email authors + {% endif %} +
    Last updated + {{ doc.time|date:"Y-m-d" }} + {% if latest_revision and latest_revision.time|date:"Y-m-d" != doc.time|date:"Y-m-d" %} + (Latest revision {{ latest_revision.time|date:"Y-m-d" }}) + {% endif %} +
    Replaces + {% if can_edit_stream_info and not snapshot %} + Edit + {% endif %} + + {% if replaces %} + {% if document_html %} + {{ replaces|urlize_related_target_list:document_html|join:"
    " }} + {% else %} + {{ replaces|urlize_related_target_list:document_html|join:", " }} + {% endif %} + {% else %} + (None) + {% endif %} +
    + Replaced by + + + {% if document_html %} + {{ replaced_by|urlize_related_source_list:document_html|join:"
    " }} + {% else %} + {{ replaced_by|urlize_related_source_list:document_html|join:", " }} + {% endif %} +
    + Possibly Replaces + + {% if can_edit_replaces and not snapshot %} + + Edit + + {% endif %} + + {% if document_html %} + {{ possibly_replaces|urlize_related_target_list:document_html|join:"
    " }} + {% else %} + {{ possibly_replaces|urlize_related_target_list:document_html|join:", " }} + {% endif %} +
    + Possibly Replaced By + + {% if can_edit_replaces and not snapshot %} + {% comment %}Edit{% endcomment %} + {% endif %} + + {% if document_html %} + {{ possibly_replaced_by|urlize_related_source_list:document_html|join:"
    " }} + {% else %} + {{ possibly_replaced_by|urlize_related_source_list:document_html|join:", " }} + {% endif %} +
    + RFC stream + + {% if can_change_stream and not snapshot %} + + Edit + + {% endif %} + + {% if stream_desc != "(None)" %} + {% if doc.stream.name|lower in 'iab,irtf,ise,editorial' %} + + {% endif %} + {% if document_html %} + {% if doc.stream.name|lower in 'iab,ietf,irtf' %} + {{ doc.stream.name|upper }} Logo + {% else %} + {{ stream_desc }} + {% endif %} + {% else %} + {{ stream_desc }} + {% endif %} + {% if doc.stream.name|lower in 'iab,irtf,ise,editorial' %} + + {% endif %} + {% else %} + {{ stream_desc }} + {% endif %} +
    + Intended RFC status + + {% if can_edit_stream_info and not snapshot %} + + Edit + + {% endif %} + + {% if doc.intended_std_level %} + {{ doc.intended_std_level }} + {% else %} + + (None) + + {% endif %} +
    + {% if document_html %}Other formats{% else %}Formats{% endif %} + + + {% if document_html %} + {% include "doc/document_format_buttons.html" with skip_format="htmlized" %} + {% else %} + {% include "doc/document_format_buttons.html" %} + {% endif %} +
    + {{ check.checker|title }} + + + {% if check.errors or check.warnings %} + + {{ check.symbol|safe }} + + {% else %} + + {{ check.symbol|safe }} + + {% endif %} + + {{ check.errors }} errors, {{ check.warnings }} warnings + + {% include "doc/yang-check-modal-overlay.html" %} +
    + Reviews + + + {% for review_assignment in review_assignments %} + {% include "doc/review_assignment_summary.html" with current_doc_name=doc.name current_rev=doc.rev %} + {% endfor %} + {% if no_review_from_teams %} + {% for team in no_review_from_teams %} + {{ team.acronym.upper }}{% if not forloop.last %},{% endif %} + {% endfor %} + will not review this version + {% endif %} + {% if can_request_review or can_submit_unsolicited_review_for_teams %} +
    + {% if can_request_review %} + + + + Request review + + {% endif %} + {% if can_submit_unsolicited_review_for_teams|length == 1 %} + + + + Submit unsolicited review + + {% elif can_submit_unsolicited_review_for_teams %} + + + + Submit unsolicited review + + {% endif %} +
    + {% endif %} +
    + IETF conflict review + + + {{ conflict_reviews|join:", "|urlize_ietf_docs }} +
    + + Additional resources + + {% if can_edit_stream_info or can_edit_individual %} + + Edit + + {% endif %} + + {% if resources or doc.group and doc.group.list_archive %} + {% for resource in resources|dictsort:"display_name" %} + {% if resource.name.type.slug == 'url' or resource.name.type.slug == 'email' %} + + {% firstof resource.display_name resource.name.name %} + +
    + {# Maybe make how a resource displays itself a method on the class so templates aren't doing this switching #} + {% else %} + + {% firstof resource.display_name resource.name.name %}: {{ resource.value|escape }} + +
    + {% endif %} + {% endfor %} + {% if doc.group and doc.group.list_archive %} + {% if doc.group.list_archive|startswith:settings.MAILING_LIST_ARCHIVE_URL %} + + Mailing list discussion + + {% elif doc.group.list_archive|is_valid_url %} + + Mailing list discussion + + {% else %} + {{ doc.group.list_archive|urlencode }} + {% endif %} + {% endif %} + {% endif %} +
    diff --git a/ietf/templates/meeting/session_details_panel.html b/ietf/templates/meeting/session_details_panel.html index 654099da5..114c4e3fd 100644 --- a/ietf/templates/meeting/session_details_panel.html +++ b/ietf/templates/meeting/session_details_panel.html @@ -69,12 +69,6 @@ {{ pres.document.title }}({{ pres.document.name }}) {% if user|has_role:"Secretariat" or can_manage_materials %} - {% if pres.document.type.slug != 'bluesheets' or user|has_role:"Secretariat" or meeting.type.slug == 'interim' and can_manage_materials %} - {% if pres.document.type.slug == 'minutes' %} - Import from notes.ietf.org - {% endif %} - Upload revision - {% endif %} {% if pres.document.type.slug == 'minutes' %} {% url 'ietf.meeting.views.upload_session_minutes' session_id=session.pk num=session.meeting.number as upload_url %} {% elif pres.document.type.slug == 'agenda' %} @@ -82,6 +76,13 @@ {% else %} {% url 'ietf.meeting.views.upload_session_bluesheets' session_id=session.pk num=session.meeting.number as upload_url %} {% endif %} + {% if pres.document.type.slug != 'bluesheets' or user|has_role:"Secretariat" or meeting.type.slug == 'interim' and can_manage_materials %} + {% if pres.document.type.slug == 'minutes' %} + Import from notes.ietf.org + {% endif %} + Upload revision + {% endif %} + {% endif %} diff --git a/ietf/templates/meeting/upcoming.html b/ietf/templates/meeting/upcoming.html index c974d7338..eece8383f 100644 --- a/ietf/templates/meeting/upcoming.html +++ b/ietf/templates/meeting/upcoming.html @@ -46,7 +46,12 @@ {% for entry in entries %} + {% if entry|classname == 'Session' %} + data-filter-keywords="{{ entry.filter_keywords|join:',' }}" + {% elif entry|classname == 'Meeting' %} + data-filter-keywords="ietf-meetings" + {% endif %} + > {% if entry|classname == 'Meeting' %} {% with meeting=entry %}
    Feedback pending from email list diff --git a/ietf/utils/decorators.py b/ietf/utils/decorators.py index 634724880..d37d255a3 100644 --- a/ietf/utils/decorators.py +++ b/ietf/utils/decorators.py @@ -105,7 +105,7 @@ def _memoize(func, self, *args, **kwargs): return cache[key] def memoize(func): if not hasattr(func, '__class__'): - raise NotImplementedError("Use @lru_cache instead of memoize() for funcitons.") + raise NotImplementedError("Use @lru_cache instead of memoize() for functions.") # For methods, we want the cache on the object, not on the class, in order # to not having to think about cache bloat and content becoming stale, so # we cannot set up the cache here. diff --git a/ietf/utils/draft.py b/ietf/utils/draft.py index 0a379b0e9..eeac1747b 100755 --- a/ietf/utils/draft.py +++ b/ietf/utils/draft.py @@ -532,13 +532,13 @@ class PlaintextDraft(Draft): indent_lines.append(indent) percents = {} total = float(len(indent_lines)) - formated = False + formatted = False for indent in set(indent_lines): count = indent_lines.count(indent)/total percents[indent] = count if count > 0.9: - formated = True - if not formated: + formatted = True + if not formatted: return abstract new_abstract = [] for line in abstract.split('\n'): @@ -592,8 +592,8 @@ class PlaintextDraft(Draft): "honor" : r"(?:[A-Z]\.|Dr\.?|Dr\.-Ing\.|Prof(?:\.?|essor)|Sir|Lady|Dame|Sri)", "prefix": r"([Dd]e|Hadi|van|van de|van der|Ver|von|[Ee]l)", "suffix": r"(jr.?|Jr.?|II|2nd|III|3rd|IV|4th)", - "first" : r"([A-Z][-A-Za-z'`~]*)(( ?\([A-Z][-A-Za-z'`~]*\))?(\.?[- ]{1,2}[A-Za-z'`~]+)*)", - "last" : r"([-A-Za-z'`~]{2,})", + "first" : r"([A-Z][-A-Za-z'`~,]*)(( ?\([A-Z][-A-Za-z'`~,]*\))?(\.?[- ]{1,2}[A-Za-z'`~]+)*)", + "last" : r"([-A-Za-z'`~,]+)", # single-letter last names exist "months": r"(January|February|March|April|May|June|July|August|September|October|November|December)", "mabbr" : r"(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\.?", } @@ -658,7 +658,12 @@ class PlaintextDraft(Draft): # permit insertion of middle names between first and last, and # add possible honorific and suffix information - authpat = r"(?:^| and )(?:%(hon)s ?)?(['`]*%(first)s\S*( +[^ ]+)* +%(last)s)( *\(.*|,( [A-Z][-A-Za-z0-9]*)?| %(suffix)s| [A-Z][a-z]+)?" % {"hon":hon, "first":first, "last":last, "suffix":suffix,} + if last: + authpat = r"(?:^| and )((?:%(hon)s ?)?['`]*%(first)s\S*( +[^ ]+)* +%(last)s(?: %(suffix)s)?)( *\(.*|,( [A-Z][-A-Za-z0-9]*)?| [A-Z][a-z]+)?" % {"hon":hon, "first":first, "last":last, "suffix":suffix,} + else: + # handle single-word names + authpat = r"(?:^| and )((?:%(hon)s ?)?['`]*%(first)s\S*( +[^ ]+)*(?: %(suffix)s)?)( *\(.*|,( [A-Z][-A-Za-z0-9]*)?| [A-Z][a-z]+)?" % {"hon":hon, "first":first, "suffix":suffix,} + return authpat authors = [] @@ -812,7 +817,7 @@ class PlaintextDraft(Draft): author = author[:-len(suffix)].strip() else: suffix = None - if "," in author: + if ", " in author: last, first = author.split(",",1) author = "%s %s" % (first.strip(), last.strip()) if not " " in author: @@ -820,8 +825,9 @@ class PlaintextDraft(Draft): first, last = author.rsplit(".", 1) first += "." else: - author = "[A-Z].+ " + author - first, last = author.rsplit(" ", 1) + # handle single-word names + first = author + last = "" else: if "." in author: first, last = author.rsplit(".", 1) @@ -899,10 +905,14 @@ class PlaintextDraft(Draft): #else: # fullname = author_match fullname = re.sub(" +", " ", fullname) - if left == firstname: - given_names, surname = fullname.rsplit(None, 1) + if re.search(r"\s", fullname): + if left == firstname: + given_names, surname = fullname.rsplit(None, 1) + else: + surname, given_names = fullname.split(None, 1) else: - surname, given_names = fullname.split(None, 1) + # handle single-word names + given_names, surname = (fullname, "") if " " in given_names: first, middle = given_names.split(None, 1) else: diff --git a/ietf/utils/mail.py b/ietf/utils/mail.py index 3500d888e..b2b9f0b9d 100644 --- a/ietf/utils/mail.py +++ b/ietf/utils/mail.py @@ -533,7 +533,7 @@ def log_smtp_exception(e): def build_warning_message(request, e): (extype, value, tb) = exception_components(e) if request: - warning = "An error occured while sending email:\n" + warning = "An error occurred while sending email:\n" if getattr(e,'original_msg',None): warning += "Subject: %s\n" % e.original_msg.get('Subject','[no subject]') warning += "To: %s\n" % e.original_msg.get('To','[no to]') diff --git a/ietf/utils/management/commands/coverage_changes.py b/ietf/utils/management/commands/coverage_changes.py index 7a445bd0c..75866226b 100644 --- a/ietf/utils/management/commands/coverage_changes.py +++ b/ietf/utils/management/commands/coverage_changes.py @@ -34,7 +34,7 @@ class Command(BaseCommand): " $ manage.py {name} --absolute --sections=url | grep False\n" "\n".format(**locals()) ) - args = "[[master_json] latest_json]" + args = "[[main_json] latest_json]" def create_parser(self, prog_name, subcommand): import argparse @@ -78,13 +78,13 @@ class Command(BaseCommand): raise CommandError("There is no data for version %s available in %s" % (version, filename)) return data[version], version - def coverage_diff(self, master, latest, sections, release=None, **options): - master_coverage, mversion = self.read_coverage(master, release) + def coverage_diff(self, main, latest, sections, release=None, **options): + main_coverage, mversion = self.read_coverage(main, release) latest_coverage, lversion = self.read_coverage(latest) self.stdout.write("\nShowing coverage differeces between %s and %s:\n" % (mversion, lversion)) for section in sections: - mcoverage = master_coverage[section]["covered"] - mformat = master_coverage[section].get("format", 1) + mcoverage = main_coverage[section]["covered"] + mformat = main_coverage[section].get("format", 1) lcoverage = latest_coverage[section]["covered"] lformat = latest_coverage[section].get("format", 1) # @@ -235,7 +235,7 @@ class Command(BaseCommand): # verbosity = int(options.get('verbosity')) if not filenames: filenames = [ - getattr(settings, 'TEST_COVERAGE_MASTER_FILE'), + getattr(settings, 'TEST_COVERAGE_MAIN_FILE'), getattr(settings, 'TEST_COVERAGE_LATEST_FILE'), ] if len(filenames) != 2: diff --git a/ietf/utils/management/commands/tests.py b/ietf/utils/management/commands/tests.py index 6619cb819..d684b31f7 100644 --- a/ietf/utils/management/commands/tests.py +++ b/ietf/utils/management/commands/tests.py @@ -15,7 +15,7 @@ import debug # pyflakes:ignore class CoverageChangeTestCase(TestCase): def test_coverage_change(self): - master_txt ="""{ + main_txt ="""{ "5.12.0": { "code": { "coverage": 0.5921474057048117, @@ -81,16 +81,16 @@ class CoverageChangeTestCase(TestCase): "version":"latest" } """ - mfh, master = tempfile.mkstemp(suffix='.json') - with io.open(master, "w") as file: - file.write(master_txt) + mfh, main = tempfile.mkstemp(suffix='.json') + with io.open(main, "w") as file: + file.write(main_txt) lfh, latest = tempfile.mkstemp(suffix='.json') with io.open(latest, "w") as file: file.write(latest_txt) output = io.StringIO() - call_command('coverage_changes', master, latest, stdout=output) + call_command('coverage_changes', main, latest, stdout=output) text = output.getvalue() - os.unlink(master) + os.unlink(main) os.unlink(latest) for l in [ diff --git a/ietf/utils/management/commands/update_external_command_info.py b/ietf/utils/management/commands/update_external_command_info.py index 9cc1d1ba4..e9e24f000 100644 --- a/ietf/utils/management/commands/update_external_command_info.py +++ b/ietf/utils/management/commands/update_external_command_info.py @@ -31,7 +31,7 @@ class Command(BaseCommand): out = out.decode('utf-8') err = err.decode('utf-8') if code != 0: - sys.stderr.write("Command '%s' retuned %s: \n%s\n%s\n" % (cmd, code, out, err)) + sys.stderr.write("Command '%s' returned %s: \n%s\n%s\n" % (cmd, code, out, err)) else: c.version = (out.strip()+'\n'+err.strip()).strip() if options.get('verbosity', 1) > 1: diff --git a/ietf/utils/test_data.py b/ietf/utils/test_data.py index 2c6f048a5..6bf502883 100644 --- a/ietf/utils/test_data.py +++ b/ietf/utils/test_data.py @@ -347,7 +347,7 @@ def make_test_data(): title="Statement regarding rights", holder_legal_name="Native Martians United", state=IprDisclosureStateName.objects.get(slug='posted'), - patent_info='Number: US12345\nTitle: A method of transfering bits\nInventor: A. Nonymous\nDate: 2000-01-01', + patent_info='Number: US12345\nTitle: A method of transferring bits\nInventor: A. Nonymous\nDate: 2000-01-01', holder_contact_name='George', holder_contact_email='george@acme.com', holder_contact_info='14 Main Street\nEarth', diff --git a/ietf/utils/test_runner.py b/ietf/utils/test_runner.py index 3e53808af..3a2251213 100644 --- a/ietf/utils/test_runner.py +++ b/ietf/utils/test_runner.py @@ -174,14 +174,21 @@ def vnu_fmt_message(file, msg, content): def vnu_filter_message(msg, filter_db_issues, filter_test_issues): "True if the vnu message is a known false positive" - if filter_db_issues and re.search( - r"""^Forbidden\ code\ point\ U\+| - Illegal\ character\ in\ query:\ '\['| - 'href'\ on\ element\ 'a':\ Percentage\ \("%"\)\ is\ not\ followed| - ^Saw\ U\+\d+\ in\ stream| - ^Document\ uses\ the\ Unicode\ Private\ Use\ Area""", + if re.search( + r"""^Document\ uses\ the\ Unicode\ Private\ Use\ Area| + ^Element\ 'h.'\ not\ allowed\ as\ child\ of\ element\ 'pre'""", msg["message"], flags=re.VERBOSE, + ) or ( + filter_db_issues + and re.search( + r"""^Forbidden\ code\ point\ U\+| + Illegal\ character\ in\ query:\ '\['| + 'href'\ on\ element\ 'a':\ Percentage\ \("%"\)\ is\ not| + ^Saw\ U\+\d+\ in\ stream""", + msg["message"], + flags=re.VERBOSE, + ) ): return True @@ -196,7 +203,6 @@ def vnu_filter_message(msg, filter_db_issues, filter_test_issues): return re.search( r"""document\ is\ not\ mappable\ to\ XML\ 1| - Attribute\ 'readonly'\ not\ allowed\ on\ element\ 'select'| ^Attribute\ 'required'\ not\ allowed\ on\ element\ 'div'| ^The\ 'type'\ attribute\ is\ unnecessary\ for\ JavaScript| is\ not\ in\ Unicode\ Normalization\ Form\ C""", @@ -727,7 +733,7 @@ class IetfTestRunner(DiscoverRunner): settings.show_logging = show_logging # self.root_dir = os.path.dirname(settings.BASE_DIR) - self.coverage_file = os.path.join(self.root_dir, settings.TEST_COVERAGE_MASTER_FILE) + self.coverage_file = os.path.join(self.root_dir, settings.TEST_COVERAGE_MAIN_FILE) super(IetfTestRunner, self).__init__(**kwargs) if self.parallel > 1: if self.html_report == True: diff --git a/package.json b/package.json index a5a960c9a..2a34ab0ed 100644 --- a/package.json +++ b/package.json @@ -20,23 +20,23 @@ "bootstrap": "5.2.3", "bootstrap-icons": "1.10.2", "browser-fs-access": "0.31.1", - "caniuse-lite": "1.0.30001434", + "caniuse-lite": "1.0.30001435", "d3": "7.6.1", "file-saver": "2.0.5", - "highcharts": "10.3.1", + "highcharts": "10.3.2", "jquery": "3.6.1", "jquery-ui-dist": "1.13.2", "js-cookie": "3.0.1", "list.js": "2.3.1", "lodash": "4.17.21", "lodash-es": "4.17.21", - "luxon": "3.1.0", + "luxon": "3.1.1", "moment": "2.29.4", "moment-timezone": "0.5.39", "ms": "2.1.3", "murmurhash-js": "1.0.0", "naive-ui": "2.34.2", - "pinia": "2.0.26", + "pinia": "2.0.27", "pinia-plugin-persist": "1.0.0", "select2": "4.1.0-rc.0", "select2-bootstrap-5-theme": "1.3.0", @@ -50,6 +50,8 @@ }, "devDependencies": { "@faker-js/faker": "7.6.0", + "@parcel/optimizer-data-url": "2.8.0", + "@parcel/transformer-inline-string": "2.8.0", "@parcel/transformer-sass": "2.8.0", "@rollup/pluginutils": "5.0.2", "@vitejs/plugin-vue": "3.2.0", @@ -76,6 +78,9 @@ "distDir": "ietf/static/dist/ietf", "source": [ "ietf/static/css/datepicker.scss", + "ietf/static/css/document_html_inline.scss", + "ietf/static/css/document_html_referenced.scss", + "ietf/static/css/document_html_txt.scss", "ietf/static/css/ietf.scss", "ietf/static/css/jquery-ui.scss", "ietf/static/css/liaisons.css", @@ -83,6 +88,7 @@ "ietf/static/css/select2.scss", "ietf/static/images/arrow-ani.webp", "ietf/static/images/iab-logo-card.png", + "ietf/static/images/iab-logo.svg", "ietf/static/images/iesg-draft-state-diagram.png", "ietf/static/images/ietf-logo-card.png", "ietf/static/images/ietf-logo-nor-16-dev.png", @@ -100,6 +106,7 @@ "ietf/static/images/ietf-logo-nor.svg", "ietf/static/images/ietf-logo.svg", "ietf/static/images/irtf-logo-card.png", + "ietf/static/images/irtf-logo.svg", "ietf/static/js/agenda_filter.js", "ietf/static/js/agenda_materials.js", "ietf/static/js/agenda_personalize.js", @@ -110,6 +117,7 @@ "ietf/static/js/d3.js", "ietf/static/js/datepicker.js", "ietf/static/js/doc-search.js", + "ietf/static/js/document_html.js", "ietf/static/js/document_relations.js", "ietf/static/js/document_timeline.js", "ietf/static/js/draft-submit.js", @@ -200,5 +208,8 @@ ] } }, + "resolutions": { + "lightningcss": "1.17.1" + }, "packageManager": "yarn@3.2.2" } diff --git a/requirements.txt b/requirements.txt index dd5349a7a..5f1ebb782 100644 --- a/requirements.txt +++ b/requirements.txt @@ -64,6 +64,6 @@ tblib>=1.7.0 # So that the django test runner provides tracebacks tlds>=2022042700 # Used to teach bleach about which TLDs currently exist tqdm>=4.64.0 Unidecode>=1.3.4 -weasyprint>=52.5,<53 # Datatracker tests past on 54, but xml2rfc tests do not. +weasyprint>=53 xml2rfc>=3.12.4 xym>=0.6,<1.0 diff --git a/yarn.lock b/yarn.lock index 341d7cfc1..d8159df08 100644 --- a/yarn.lock +++ b/yarn.lock @@ -824,6 +824,18 @@ __metadata: languageName: node linkType: hard +"@parcel/optimizer-data-url@npm:2.8.0": + version: 2.8.0 + resolution: "@parcel/optimizer-data-url@npm:2.8.0" + dependencies: + "@parcel/plugin": 2.8.0 + "@parcel/utils": 2.8.0 + isbinaryfile: ^4.0.2 + mime: ^2.4.4 + checksum: 998fb94ceea6c385c47a2337e6c911884f6580720609fd8e95b8036b8ce5860733a3028e3b7cff62e5f1b0aed2d86fda587cce8232d5e54514898fcb8e9e76ce + languageName: node + linkType: hard + "@parcel/optimizer-htmlnano@npm:2.8.0": version: 2.8.0 resolution: "@parcel/optimizer-htmlnano@npm:2.8.0" @@ -1145,6 +1157,15 @@ __metadata: languageName: node linkType: hard +"@parcel/transformer-inline-string@npm:2.8.0": + version: 2.8.0 + resolution: "@parcel/transformer-inline-string@npm:2.8.0" + dependencies: + "@parcel/plugin": 2.8.0 + checksum: e40616c55bbebfacc38a572d44028f79dab95abbe57dd27175bdce267894c7885fe1aab38012b1ce2c652f03d0d91ce5dbdc64c2625a12ae83cfff603558c259 + languageName: node + linkType: hard + "@parcel/transformer-js@npm:2.8.0": version: 2.8.0 resolution: "@parcel/transformer-js@npm:2.8.0" @@ -2075,10 +2096,10 @@ browserlist@latest: languageName: node linkType: hard -"caniuse-lite@npm:1.0.30001434": - version: 1.0.30001434 - resolution: "caniuse-lite@npm:1.0.30001434" - checksum: 7c9d2641e8e8f3ddf9af14c4ce47266a9d8fd1fc0243626049ff1b2eca4bf02938ff440813cc3feae3fa8d851ec8d1b9718044340c8d09bb4372d92d4f6b519c +"caniuse-lite@npm:1.0.30001435": + version: 1.0.30001435 + resolution: "caniuse-lite@npm:1.0.30001435" + checksum: ec88b9c37f66095e26ddb8b43110e9564ebccb6de77e495b8e8b9d64fdbfe37f7762be8fd2578c3ecc181a183a159578c9bd8e9b90eb15b44b78e8a6d0e92530 languageName: node linkType: hard @@ -3944,10 +3965,10 @@ browserlist@latest: languageName: node linkType: hard -"highcharts@npm:10.3.1": - version: 10.3.1 - resolution: "highcharts@npm:10.3.1" - checksum: 8a1cf9a363cfcd30b7ea64f6baba5c23c06998367b36eb9bee8b3903d49820df9de8251e3a6434f88a153d537243c2b41a55dc44e841c486f49e20bfe41a42d2 +"highcharts@npm:10.3.2": + version: 10.3.2 + resolution: "highcharts@npm:10.3.2" + checksum: 43cb42b24c3d6effc03d021f410a760322092ebd7012b60c2c475aa48a426af6c2a0a2937707bfc1c7aab089f0cc968c9aec3a9a1147e87ffc463b0adfb61ad4 languageName: node linkType: hard @@ -4402,6 +4423,13 @@ browserlist@latest: languageName: node linkType: hard +"isbinaryfile@npm:^4.0.2": + version: 4.0.10 + resolution: "isbinaryfile@npm:4.0.10" + checksum: a6b28db7e23ac7a77d3707567cac81356ea18bd602a4f21f424f862a31d0e7ab4f250759c98a559ece35ffe4d99f0d339f1ab884ffa9795172f632ab8f88e686 + languageName: node + linkType: hard + "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -4621,75 +4649,75 @@ browserlist@latest: languageName: node linkType: hard -"lightningcss-darwin-arm64@npm:1.16.1": - version: 1.16.1 - resolution: "lightningcss-darwin-arm64@npm:1.16.1" +"lightningcss-darwin-arm64@npm:1.17.1": + version: 1.17.1 + resolution: "lightningcss-darwin-arm64@npm:1.17.1" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"lightningcss-darwin-x64@npm:1.16.1": - version: 1.16.1 - resolution: "lightningcss-darwin-x64@npm:1.16.1" +"lightningcss-darwin-x64@npm:1.17.1": + version: 1.17.1 + resolution: "lightningcss-darwin-x64@npm:1.17.1" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"lightningcss-linux-arm-gnueabihf@npm:1.16.1": - version: 1.16.1 - resolution: "lightningcss-linux-arm-gnueabihf@npm:1.16.1" +"lightningcss-linux-arm-gnueabihf@npm:1.17.1": + version: 1.17.1 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.17.1" conditions: os=linux & cpu=arm languageName: node linkType: hard -"lightningcss-linux-arm64-gnu@npm:1.16.1": - version: 1.16.1 - resolution: "lightningcss-linux-arm64-gnu@npm:1.16.1" +"lightningcss-linux-arm64-gnu@npm:1.17.1": + version: 1.17.1 + resolution: "lightningcss-linux-arm64-gnu@npm:1.17.1" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"lightningcss-linux-arm64-musl@npm:1.16.1": - version: 1.16.1 - resolution: "lightningcss-linux-arm64-musl@npm:1.16.1" +"lightningcss-linux-arm64-musl@npm:1.17.1": + version: 1.17.1 + resolution: "lightningcss-linux-arm64-musl@npm:1.17.1" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"lightningcss-linux-x64-gnu@npm:1.16.1": - version: 1.16.1 - resolution: "lightningcss-linux-x64-gnu@npm:1.16.1" +"lightningcss-linux-x64-gnu@npm:1.17.1": + version: 1.17.1 + resolution: "lightningcss-linux-x64-gnu@npm:1.17.1" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"lightningcss-linux-x64-musl@npm:1.16.1": - version: 1.16.1 - resolution: "lightningcss-linux-x64-musl@npm:1.16.1" +"lightningcss-linux-x64-musl@npm:1.17.1": + version: 1.17.1 + resolution: "lightningcss-linux-x64-musl@npm:1.17.1" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"lightningcss-win32-x64-msvc@npm:1.16.1": - version: 1.16.1 - resolution: "lightningcss-win32-x64-msvc@npm:1.16.1" +"lightningcss-win32-x64-msvc@npm:1.17.1": + version: 1.17.1 + resolution: "lightningcss-win32-x64-msvc@npm:1.17.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"lightningcss@npm:^1.16.1": - version: 1.16.1 - resolution: "lightningcss@npm:1.16.1" +"lightningcss@npm:1.17.1": + version: 1.17.1 + resolution: "lightningcss@npm:1.17.1" dependencies: detect-libc: ^1.0.3 - lightningcss-darwin-arm64: 1.16.1 - lightningcss-darwin-x64: 1.16.1 - lightningcss-linux-arm-gnueabihf: 1.16.1 - lightningcss-linux-arm64-gnu: 1.16.1 - lightningcss-linux-arm64-musl: 1.16.1 - lightningcss-linux-x64-gnu: 1.16.1 - lightningcss-linux-x64-musl: 1.16.1 - lightningcss-win32-x64-msvc: 1.16.1 + lightningcss-darwin-arm64: 1.17.1 + lightningcss-darwin-x64: 1.17.1 + lightningcss-linux-arm-gnueabihf: 1.17.1 + lightningcss-linux-arm64-gnu: 1.17.1 + lightningcss-linux-arm64-musl: 1.17.1 + lightningcss-linux-x64-gnu: 1.17.1 + lightningcss-linux-x64-musl: 1.17.1 + lightningcss-win32-x64-msvc: 1.17.1 dependenciesMeta: lightningcss-darwin-arm64: optional: true @@ -4707,7 +4735,7 @@ browserlist@latest: optional: true lightningcss-win32-x64-msvc: optional: true - checksum: 78ec1fa15804351109c63d7e88cfb02a1a286a5a112847a4a6e2f57f52ff94e0b84662c079d377ec6d06a9462fed5e25db4958ef129e78f13b69077d062f7cb9 + checksum: 0bf9d5c9321db457dd25c47281b7a8af36377ede05a45daa894f1f9070fe70b9db1325646aa2a574f0212e5f961f0f57b0419847141a4f49321bca72169aef16 languageName: node linkType: hard @@ -4823,10 +4851,10 @@ browserlist@latest: languageName: node linkType: hard -"luxon@npm:3.1.0": - version: 3.1.0 - resolution: "luxon@npm:3.1.0" - checksum: f8a850b759ba7a2e009d904c522ed7bc264bf4add57578f8948e52a0ed96b627b025b5aad8032295b570ae19fac41f0ffab91bdb128715fb0cc020798a7ba886 +"luxon@npm:3.1.1": + version: 3.1.1 + resolution: "luxon@npm:3.1.1" + checksum: 388fb35d3c51a19d8b305a3338e7e74634b08562e7d2f9ed5c05a7f4bc9ee1c1ab6a2546b6d9c4c104516b24043757d65f5f3fe3d78b206fbf55a9586ab62230 languageName: node linkType: hard @@ -4888,6 +4916,15 @@ browserlist@latest: languageName: node linkType: hard +"mime@npm:^2.4.4": + version: 2.6.0 + resolution: "mime@npm:2.6.0" + bin: + mime: cli.js + checksum: 1497ba7b9f6960694268a557eae24b743fd2923da46ec392b042469f4b901721ba0adcf8b0d3c2677839d0e243b209d76e5edcbd09cfdeffa2dfb6bb4df4b862 + languageName: node + linkType: hard + "minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -5520,9 +5557,9 @@ browserlist@latest: languageName: node linkType: hard -"pinia@npm:2.0.26": - version: 2.0.26 - resolution: "pinia@npm:2.0.26" +"pinia@npm:2.0.27": + version: 2.0.27 + resolution: "pinia@npm:2.0.27" dependencies: "@vue/devtools-api": ^6.4.5 vue-demi: "*" @@ -5535,7 +5572,7 @@ browserlist@latest: optional: true typescript: optional: true - checksum: 0d38cc0efc6572436c439491491df0e989182bad342fc3828d8865a42db6c6580c8f86663fcc47955bfeca5baf6e7f95e61b9b3f795ba161c19ef86fdc9b2841 + checksum: 29c862ea4304cdfeae385cbd2ab08100809aa9a79c147c4cb4085215601c0323e0ca664cea6abcaa0298e7ea53865117b6950ebb59224347fbc1683dbe956ac8 languageName: node linkType: hard @@ -6011,6 +6048,8 @@ browserlist@latest: "@fullcalendar/luxon2": 5.11.3 "@fullcalendar/timegrid": 5.11.3 "@fullcalendar/vue3": 5.11.3 + "@parcel/optimizer-data-url": 2.8.0 + "@parcel/transformer-inline-string": 2.8.0 "@parcel/transformer-sass": 2.8.0 "@popperjs/core": 2.11.6 "@rollup/pluginutils": 5.0.2 @@ -6021,7 +6060,7 @@ browserlist@latest: browser-fs-access: 0.31.1 browserlist: latest c8: 7.12.0 - caniuse-lite: 1.0.30001434 + caniuse-lite: 1.0.30001435 d3: 7.6.1 eslint: 8.28.0 eslint-config-standard: 17.0.0 @@ -6032,7 +6071,7 @@ browserlist@latest: eslint-plugin-promise: 6.1.1 eslint-plugin-vue: 9.8.0 file-saver: 2.0.5 - highcharts: 10.3.1 + highcharts: 10.3.2 html-validate: 7.10.0 jquery: 3.6.1 jquery-migrate: 3.4.0 @@ -6041,14 +6080,14 @@ browserlist@latest: list.js: 2.3.1 lodash: 4.17.21 lodash-es: 4.17.21 - luxon: 3.1.0 + luxon: 3.1.1 moment: 2.29.4 moment-timezone: 0.5.39 ms: 2.1.3 murmurhash-js: 1.0.0 naive-ui: 2.34.2 parcel: 2.8.0 - pinia: 2.0.26 + pinia: 2.0.27 pinia-plugin-persist: 1.0.0 pug: 3.0.2 sass: 1.56.1