diff --git a/.github/workflows/deploy-backend-dev.yml b/.github/workflows/deploy-backend-dev.yml index 22cb95f73..1131c1f63 100644 --- a/.github/workflows/deploy-backend-dev.yml +++ b/.github/workflows/deploy-backend-dev.yml @@ -29,3 +29,12 @@ jobs: GCP_SA_KEY: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }} ASSEMBLY_API_KEY: ${{ secrets.ASSEMBLY_API_KEY }} PROJECT_ID: digital-testimony-dev + + # Update Typesense Schema + - uses: google-github-actions/auth@v3 + with: + credentials_json: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }} + - uses: google-github-actions/setup-gcloud@v3 + - name: Update Typesense Schema + run: | + gcloud pubsub topics publish --project=digital-testimony-dev checkSearchIndexVersion --message='{"check": true}' diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index 8c68eead9..a4a79defd 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -23,3 +23,12 @@ jobs: GCP_SA_KEY: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }} ASSEMBLY_API_KEY: ${{ secrets.ASSEMBLY_API_KEY }} PROJECT_ID: digital-testimony-prod + + # Update Typesense Schema + - uses: google-github-actions/auth@v3 + with: + credentials_json: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }} + - uses: google-github-actions/setup-gcloud@v3 + - name: Update Typesense Schema + run: | + gcloud pubsub topics publish --project=digital-testimony-prod checkSearchIndexVersion --message='{"check": true}' diff --git a/README.md b/README.md index 01b7f2845..f0794be4e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ We are creating a new web platform called MAPLE (the Massachusetts Platform for ## Essentials -Join the [Code for Boston Slack](https://communityinviter.com/apps/cfb-public/default-badge) and our `#maple-testimony` channel. Ask to join the Zenhub project and to be added as a collaborator on Github, and provide your Github username. +Join the [Code for Boston Slack](https://communityinviter.com/apps/cfb-public/default-badge) and our `#maple-testimony` channel. Ask to be added as a collaborator on Github, and provide your Github username. Attend a [weekly hack night at Code for Boston](https://www.meetup.com/code-for-boston/events/) and join our group. @@ -16,7 +16,7 @@ Check out the [Contributing](./Contributing.md) docs for how to contribute to th ## Links -- [Zenhub project board](https://app.zenhub.com/workspaces/design-and-development-629389aa02e9d200139c90b8/board), where issues are organized +- [GitHub issues](https://github.com/codeforboston/maple/issues), where issues are organized - [Figma Designs](), where UI designs are organized - [Chromatic Storybook Library](https://www.chromatic.com/library?appId=634f3926f2a0d0f0195eefd7&branch=main), where our React UI component library is documented. - [Maple Documentation on the Wiki](https://github.com/codeforboston/maple/wiki) @@ -253,4 +253,4 @@ Thanks to all our contributors! -This table follows the [All Contributors](https://allcontributors.org/) specification and is managed by the @all-contributors bot. You can add yourself or another contributor by [commenting on an issue or a pull request](https://allcontributors.org/docs/en/bot/usage). +This table follows the [All Contributors](https://allcontributors.org/) specification and is managed by the @all-contributors bot. You can add yourself or another contributor by [commenting on an issue or a pull request](https://allcontributors.org/bot/usage/). diff --git a/components/AboutPagesCard/AboutPagesCard.tsx b/components/AboutPagesCard/AboutPagesCard.tsx index b74e00d16..6fbf18b41 100644 --- a/components/AboutPagesCard/AboutPagesCard.tsx +++ b/components/AboutPagesCard/AboutPagesCard.tsx @@ -25,7 +25,7 @@ const AboutPagesCard: FC> = ({ children }) => { return ( - +
-

- {t("verifyAccountSection.verifyAccount")} -

+

{t("verifyAccountSection.verifyAccount")}

{sendEmailVerification.status === "success" ? ( @@ -35,7 +33,7 @@ export const VerifyAccountSection = ({ {sendEmailVerification.status !== "success" ? ( sendEmailVerification.execute(user)} diff --git a/components/bill/BillDetails.tsx b/components/bill/BillDetails.tsx index 7dc5f608a..d4d8a363a 100644 --- a/components/bill/BillDetails.tsx +++ b/components/bill/BillDetails.tsx @@ -15,22 +15,32 @@ import { Back } from "components/shared/CommonComponents" import { useFlags } from "components/featureFlags" import { FollowBillButton } from "components/shared/FollowButton" import { PendingUpgradeBanner } from "components/PendingUpgradeBanner" -import { isCurrentCourt } from "functions/src/shared" +import { + currentBallotInitiativeCommittee, + isCurrentCourt +} from "functions/src/shared" export const BillDetails = ({ bill }: BillProps) => { const { t } = useTranslation("common") const isPendingUpgrade = useAuth().claims?.role === "pendingUpgrade" const flags = useFlags() - const { user } = useAuth() + let isBallotMeasure = false + const curComm = bill?.currentCommittee?.id + + if (curComm === currentBallotInitiativeCommittee) { + isBallotMeasure = true + } + return ( <> {isPendingUpgrade && } {!isCurrentCourt(bill.court) && ( {t("bill.old_session", { billCourt: bill.court })} )} + {isBallotMeasure && {t("bill.ballot_initiative")}} @@ -74,14 +84,19 @@ export const BillDetails = ({ bill }: BillProps) => { )} - + - + - + {isBallotMeasure ? ( + <> + ) : ( + + )} + {flags.lobbyingTable && ( diff --git a/components/bill/Summary.tsx b/components/bill/Summary.tsx index 458dd8675..4b1696055 100644 --- a/components/bill/Summary.tsx +++ b/components/bill/Summary.tsx @@ -17,7 +17,10 @@ import { SmartIcon } from "./SmartIcon" import { TestimonyCounts } from "./TestimonyCounts" import { BillProps } from "./types" import { BillTopic } from "functions/src/bills/types" -import { currentGeneralCourt } from "functions/src/shared" +import { + currentBallotInitiativeCommittee, + currentGeneralCourt +} from "functions/src/shared" const Divider = styled(Col)` width: 2px; @@ -30,6 +33,10 @@ const FormattedBillDetails = styled(Col)` white-space: pre-wrap; ` +const BallotSummaryRow = styled(Row)` + white-space: pre-wrap; +` + const SmartTag = ({ topic }: { topic: BillTopic }) => { return ( setShowBillDetails(false) const billText = bill?.content?.DocumentText const hearingIds = bill?.hearingIds + const isBallotMeasure = + bill?.currentCommittee?.id === currentBallotInitiativeCommittee const { showLLMFeatures } = useFlags() @@ -203,10 +212,20 @@ export const Summary = ({
+ ) : bill.summary !== undefined && isBallotMeasure ? ( + <> +
+ ) : ( <> )} - {bill.summary} + {bill.summary !== undefined && isBallotMeasure ? ( + + {bill.summary} + + ) : ( + {bill.summary} + )} {bill.topics?.map(t => ( diff --git a/components/buttons.tsx b/components/buttons.tsx index 9758b45f0..723914368 100644 --- a/components/buttons.tsx +++ b/components/buttons.tsx @@ -341,6 +341,48 @@ export const CopyButton = ({ ) } +export const ShareLinkButton = ({ + text, + tooltipDurationMs = 1000, + children, + format = "text/html", + ...props +}: ButtonProps & { + text: string + tooltipDurationMs?: number + format?: string +}) => { + const { t } = useTranslation("common") + const [show, setShow] = useState(false) + const target = useRef(null) + const closeTimeout = useRef() + return ( + <> + { + if (success) { + clearTimeout(closeTimeout.current) + setShow(true) + closeTimeout.current = setTimeout( + () => setShow(false), + tooltipDurationMs + ) + } + }} + > + + + + {props => {t("copiedToClipboard")}} + + + ) +} + export const GearIcon = (
{ const { t } = useTranslation(["common", "hearing"]) - const [transcriptData, setTranscriptData] = useState(null) + const router = useRouter() + const [transcriptData, setTranscriptData] = useState(null) const [videoLoaded, setVideoLoaded] = useState(false) + const handleVideoLoad = () => { setVideoLoaded(true) } @@ -63,6 +69,15 @@ export const HearingDetails = ({ videoRef.current ? (videoRef.current.currentTime = value) : null } + useEffect(() => { + const startTime = router.query.t + const resultString: string = convertToString(startTime) + + if (startTime && videoRef.current) { + setCurTimeVideo(parseInt(resultString, 10)) + } + }, [router.query.t, videoRef.current]) + useEffect(() => { ;(async function () { if (!videoTranscriptionId || transcriptData !== null) return @@ -169,6 +184,7 @@ export const HearingDetails = ({ {transcriptData ? ( ([]) + const [initialScrollTarget, setInitialScrollTarget] = useState( + null + ) + const hasScrolledToInitial = useRef(false) const handleClearInput = () => { setSearchTerm("") } + // Shared function to scroll to a transcript index + const scrollToTranscript = (index: number) => { + const container = containerRef.current + const elem = transcriptRefs.current.get(index) + + if (elem && container) { + const elemTop = elem.offsetTop - container.offsetTop + const elemBottom = elemTop + elem.offsetHeight + const viewTop = container.scrollTop + const viewBottom = viewTop + container.clientHeight + + if (elemTop < viewTop) { + container.scrollTo({ + top: elemTop, + behavior: "smooth" + }) + } else if (elemBottom > viewBottom) { + container.scrollTo({ + top: elemBottom - container.clientHeight, + behavior: "smooth" + }) + } + } + } + useEffect(() => { setFilteredData( transcriptData.filter(el => @@ -145,32 +195,51 @@ export const Transcriptions = ({ ) }, [transcriptData, searchTerm]) + const router = useRouter() + const startTime = router.query.t + const resultString: string = convertToString(startTime) + + let currentIndex = transcriptData.findIndex( + element => parseInt(resultString, 10) <= element.end / 1000 + ) + + // Set the initial scroll target when we have a startTime and transcripts + useEffect(() => { + if ( + startTime && + transcriptData.length > 0 && + currentIndex !== -1 && + !hasScrolledToInitial.current + ) { + setInitialScrollTarget(currentIndex) + } + }, [startTime, transcriptData, currentIndex]) + + // Scroll to the initial target when the ref becomes available + useEffect(() => { + if (initialScrollTarget !== null && !searchTerm) { + const elem = transcriptRefs.current.get(initialScrollTarget) + + if (elem) { + setHighlightedId(initialScrollTarget) + scrollToTranscript(initialScrollTarget) + hasScrolledToInitial.current = true + setInitialScrollTarget(null) + } + } + }, [initialScrollTarget, transcriptRefs.current.size, searchTerm]) + useEffect(() => { const handleTimeUpdate = () => { - const currentIndex = transcriptData.findIndex( - element => videoRef.current.currentTime <= element.end / 1000 - ) + videoLoaded + ? (currentIndex = transcriptData.findIndex( + element => videoRef.current.currentTime <= element.end / 1000 + )) + : null if (containerRef.current && currentIndex !== highlightedId) { setHighlightedId(currentIndex) if (currentIndex !== -1 && !searchTerm) { - const container = containerRef.current - const elem = transcriptRefs.current.get(currentIndex) - const elemTop = elem.offsetTop - container.offsetTop - const elemBottom = elemTop + elem.offsetHeight - const viewTop = container.scrollTop - const viewBottom = viewTop + container.clientHeight - - if (elemTop < viewTop) { - container.scrollTo({ - top: elemTop, - behavior: "smooth" - }) - } else if (elemBottom > viewBottom) { - container.scrollTo({ - top: elemBottom - container.clientHeight, - behavior: "smooth" - }) - } + scrollToTranscript(currentIndex) } } } @@ -217,6 +286,7 @@ export const Transcriptions = ({ { @@ -249,12 +319,14 @@ export const Transcriptions = ({ const TranscriptItem = forwardRef(function TranscriptItem( { element, + hearingId, highlightedId, index, setCurTimeVideo, searchTerm }: { element: Paragraph + hearingId: string highlightedId: number index: number setCurTimeVideo: any @@ -270,11 +342,13 @@ const TranscriptItem = forwardRef(function TranscriptItem( set currentTime property of