mirror of
https://github.com/hanzo-apps/dataroom.git
synced 2026-10-30 14:47:36 +00:00
* feat: add option to allow text selection on Notion pages Add enableTextSelection field to Link model that allows document owners to toggle text selection/copying for visitors on Notion pages. Changes: - Add enableTextSelection Boolean field to Link Prisma schema - Add database migration for the new field - Create TextSelectionSection toggle component for link settings - Wire the setting through link sheet, link options, API routes - Pass textSelectionEnabled prop to NotionPage viewer component - Add .notion-text-selection-enabled CSS class that overrides user-select: none when text selection is allowed - Update link-active-controls to show when text selection is active - Update webhooks and link data queries to include new field By default, text selection remains disabled (preserving existing behavior). When enabled, visitors can select and copy text content on Notion document pages. Co-authored-by: Marc Seitz <[email protected]> * refactor: use team feature flag for text selection instead of per-link toggle Replace the per-link enableTextSelection database field with a team-level feature flag (textSelection) using the existing Vercel Edge Config system. This means text selection on Notion pages is controlled at the team level via the betaFeatures edge config, so there's no per-link toggle to manage. Changes: - Remove enableTextSelection from Link Prisma schema and migration - Remove TextSelectionSection toggle component and link sheet wiring - Add 'textSelection' to BetaFeatures type in featureFlags - Fetch textSelection flag in all view page getStaticProps: - pages/view/[linkId]/index.tsx (document + dataroom paths) - pages/view/domains/[domain]/[slug]/index.tsx (document + dataroom) - pages/view/[linkId]/d/[documentId].tsx (dataroom document) - pages/view/domains/[domain]/[slug]/d/[documentId].tsx (domain dataroom doc) - Pass textSelectionEnabled prop through component chain: DocumentView -> ViewData -> NotionPage DataroomDocumentView -> ViewData -> NotionPage - Keep CSS override (.notion-text-selection-enabled) and NotionPage conditional class application from previous commit To enable: add the team's ID to the 'textSelection' array in Vercel Edge Config betaFeatures. Co-authored-by: Marc Seitz <[email protected]> --------- Co-authored-by: Hanzo Dev <[email protected]> Co-authored-by: Marc Seitz <[email protected]>
514 lines
12 KiB
CSS
514 lines
12 KiB
CSS
/*
|
|
* Notion layout:
|
|
* - Regular content (text, headings, lists): 708px max
|
|
* - Tables and wide elements: up to 990px on desktop
|
|
*/
|
|
:root {
|
|
--notion-max-width: 990px;
|
|
}
|
|
|
|
.dark-mode {
|
|
--bg-color: rgb(25, 25, 25);
|
|
}
|
|
|
|
/*
|
|
* Constrain regular content blocks to 708px (Notion's default text width).
|
|
* Tables and collections will use the full 990px container width.
|
|
*/
|
|
.notion-text,
|
|
.notion-h1,
|
|
.notion-h2,
|
|
.notion-h3,
|
|
.notion-hr,
|
|
.notion-quote,
|
|
.notion-callout,
|
|
.notion-toggle,
|
|
.notion-bulleted_list,
|
|
.notion-numbered_list,
|
|
.notion-to_do,
|
|
.notion-list,
|
|
.notion-code,
|
|
.notion-bookmark,
|
|
.notion-column_list {
|
|
max-width: 708px;
|
|
}
|
|
|
|
/* Tables size to content, scroll horizontally if needed */
|
|
.notion-simple-table {
|
|
display: block;
|
|
max-width: 100%;
|
|
overflow-x: auto;
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
|
|
.notion-simple-table > table {
|
|
table-layout: auto;
|
|
width: auto; /* Size to content, don't force full width */
|
|
}
|
|
|
|
/* Give table cells proper spacing */
|
|
.notion-simple-table td {
|
|
padding: 8px 10px;
|
|
}
|
|
|
|
/* On mobile, everything uses full width */
|
|
@media (max-width: 768px) {
|
|
.notion-text,
|
|
.notion-h1,
|
|
.notion-h2,
|
|
.notion-h3,
|
|
.notion-hr,
|
|
.notion-quote,
|
|
.notion-callout,
|
|
.notion-toggle,
|
|
.notion-bulleted_list,
|
|
.notion-numbered_list,
|
|
.notion-to_do,
|
|
.notion-list,
|
|
.notion-code,
|
|
.notion-bookmark,
|
|
.notion-column_list {
|
|
max-width: 100%;
|
|
}
|
|
}
|
|
|
|
/* Disable user selection for all text content (default) */
|
|
.notion-page,
|
|
.notion-text,
|
|
.notion-h1,
|
|
.notion-h2,
|
|
.notion-h3,
|
|
.notion-h4,
|
|
.notion-h5,
|
|
.notion-h6,
|
|
.notion-header,
|
|
.notion-sub_header,
|
|
.notion-sub_sub_header,
|
|
.notion-quote,
|
|
.notion-callout,
|
|
.notion-toggle,
|
|
.notion-list,
|
|
.notion-numbered-list,
|
|
.notion-to_do,
|
|
.notion-code,
|
|
.notion-simple-table,
|
|
.notion-table,
|
|
.notion-column-list,
|
|
.notion-bookmark,
|
|
.notion-embed,
|
|
.notion-database {
|
|
-webkit-user-select: none;
|
|
-moz-user-select: none;
|
|
-ms-user-select: none;
|
|
user-select: none;
|
|
}
|
|
|
|
/* Re-enable user selection when text selection is allowed */
|
|
.notion-text-selection-enabled .notion-page,
|
|
.notion-text-selection-enabled .notion-text,
|
|
.notion-text-selection-enabled .notion-h1,
|
|
.notion-text-selection-enabled .notion-h2,
|
|
.notion-text-selection-enabled .notion-h3,
|
|
.notion-text-selection-enabled .notion-h4,
|
|
.notion-text-selection-enabled .notion-h5,
|
|
.notion-text-selection-enabled .notion-h6,
|
|
.notion-text-selection-enabled .notion-header,
|
|
.notion-text-selection-enabled .notion-sub_header,
|
|
.notion-text-selection-enabled .notion-sub_sub_header,
|
|
.notion-text-selection-enabled .notion-quote,
|
|
.notion-text-selection-enabled .notion-callout,
|
|
.notion-text-selection-enabled .notion-toggle,
|
|
.notion-text-selection-enabled .notion-list,
|
|
.notion-text-selection-enabled .notion-numbered-list,
|
|
.notion-text-selection-enabled .notion-to_do,
|
|
.notion-text-selection-enabled .notion-code,
|
|
.notion-text-selection-enabled .notion-simple-table,
|
|
.notion-text-selection-enabled .notion-table,
|
|
.notion-text-selection-enabled .notion-column-list,
|
|
.notion-text-selection-enabled .notion-bookmark,
|
|
.notion-text-selection-enabled .notion-embed,
|
|
.notion-text-selection-enabled .notion-database {
|
|
-webkit-user-select: text;
|
|
-moz-user-select: text;
|
|
-ms-user-select: text;
|
|
user-select: text;
|
|
}
|
|
|
|
/* Disable image dragging */
|
|
.notion-image,
|
|
.notion-asset,
|
|
img {
|
|
/* Prevent long-press context menu on mobile */
|
|
-webkit-touch-callout: none;
|
|
-webkit-user-select: none;
|
|
-moz-user-select: none;
|
|
-ms-user-select: none;
|
|
user-select: none;
|
|
|
|
/* Prevent image highlighting */
|
|
-webkit-tap-highlight-color: transparent;
|
|
|
|
/* Disable pointer events for saving */
|
|
pointer-events: auto;
|
|
}
|
|
|
|
/* Re-enable pointer events for links to keep them clickable */
|
|
.notion-link,
|
|
.notion-link *,
|
|
a,
|
|
a * {
|
|
pointer-events: auto !important;
|
|
cursor: pointer !important;
|
|
}
|
|
|
|
/* Ensure buttons and interactive elements remain clickable */
|
|
button,
|
|
input,
|
|
select,
|
|
textarea,
|
|
.notion-toggle-button,
|
|
.notion-page-icon {
|
|
pointer-events: auto !important;
|
|
cursor: pointer !important;
|
|
}
|
|
|
|
/* Prevent page-level horizontal overflow */
|
|
.notion-frame {
|
|
max-width: 100%;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
/* Allow notion-page to show icon without clipping, content handles its own overflow */
|
|
.notion-page {
|
|
max-width: 100%;
|
|
}
|
|
|
|
.notion-page-content {
|
|
max-width: 100%;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
/* Table overflow handling - make tables horizontally scrollable */
|
|
.notion-table {
|
|
width: 100%;
|
|
display: block;
|
|
overflow-x: auto;
|
|
-webkit-overflow-scrolling: touch;
|
|
max-width: 100%;
|
|
}
|
|
|
|
/* Ensure table content can scroll when needed */
|
|
.notion-table > table {
|
|
min-width: max-content;
|
|
}
|
|
|
|
.notion-simple-table-row td {
|
|
vertical-align: top;
|
|
}
|
|
|
|
/* Collection/Database table wrapper for horizontal scroll */
|
|
.notion-collection {
|
|
max-width: 100%;
|
|
overflow-x: auto;
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
|
|
.notion-collection-table {
|
|
overflow-x: auto;
|
|
-webkit-overflow-scrolling: touch;
|
|
max-width: 100%;
|
|
}
|
|
|
|
.notion-collection-table-wrapper {
|
|
overflow-x: auto;
|
|
-webkit-overflow-scrolling: touch;
|
|
max-width: 100%;
|
|
}
|
|
|
|
/* Ensure the table body allows scrolling */
|
|
.notion-collection-table-body {
|
|
overflow-x: auto;
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
|
|
/* Mobile-specific table overflow handling */
|
|
@media (max-width: 768px) {
|
|
.notion-table,
|
|
.notion-collection,
|
|
.notion-collection-table,
|
|
.notion-collection-table-wrapper {
|
|
max-width: calc(100vw - 2rem);
|
|
overflow-x: auto;
|
|
margin-left: 0;
|
|
margin-right: 0;
|
|
}
|
|
|
|
/* Simple tables on mobile - contained width with scroll */
|
|
.notion-simple-table {
|
|
max-width: calc(100vw - 2rem);
|
|
overflow-x: auto;
|
|
}
|
|
|
|
/* Ensure full-width tables on mobile have proper containment */
|
|
.notion-page-content-inner {
|
|
max-width: 100%;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
/* Prevent body/html from scrolling horizontally */
|
|
.notion-app,
|
|
.notion-frame {
|
|
max-width: 100vw;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
.notion-page {
|
|
max-width: 100vw;
|
|
}
|
|
}
|
|
|
|
/* Video blocks - match Notion's video layout */
|
|
.notion-asset-wrapper video {
|
|
width: 100%;
|
|
max-width: 100%;
|
|
border-radius: 4px;
|
|
display: block;
|
|
background: #000;
|
|
}
|
|
|
|
/* Ensure video containers have proper aspect ratio handling */
|
|
.notion-asset-wrapper:has(video) {
|
|
width: 100%;
|
|
max-width: 100%;
|
|
}
|
|
|
|
/* Video iframe embeds (YouTube, Vimeo, etc.) */
|
|
.notion-asset-wrapper iframe {
|
|
border-radius: 4px;
|
|
}
|
|
|
|
/* YouTube lite embed styling */
|
|
.notion-yt-lite {
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Ensure asset wrappers for videos take full width by default */
|
|
.notion-asset-wrapper-video {
|
|
width: 100% !important;
|
|
max-width: 100%;
|
|
}
|
|
|
|
/* Video block specific layout - match Notion's page width behavior */
|
|
.notion-video {
|
|
width: 100%;
|
|
max-width: 100%;
|
|
}
|
|
|
|
/* Embed blocks (external videos like Sieve, etc.) - ensure proper aspect ratio */
|
|
.notion-asset-wrapper:has(iframe) {
|
|
width: 100%;
|
|
}
|
|
|
|
/* Fix for video embeds that don't have explicit dimensions */
|
|
.notion-asset-wrapper > div[style*="padding-bottom"] {
|
|
width: 100%;
|
|
}
|
|
|
|
/* Ensure external embed iframes fill their container */
|
|
.notion-asset-wrapper iframe.notion-asset-object-fit {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
/* Video wrapper default aspect ratio when not specified */
|
|
.notion-asset-wrapper > div:has(> video):not([style*="padding-bottom"]):not([style*="height"]) {
|
|
aspect-ratio: 16 / 9;
|
|
}
|
|
|
|
/* Ensure videos without explicit sizing get proper dimensions */
|
|
.notion-asset-wrapper video:not([style*="width"]) {
|
|
width: 100%;
|
|
}
|
|
|
|
.notion-asset-wrapper video:not([style*="height"]) {
|
|
height: auto;
|
|
}
|
|
|
|
/*
|
|
* Fix for native video elements that don't have aspect ratio set by react-notion-x
|
|
* react-notion-x removes paddingBottom for video blocks, so we need to ensure
|
|
* videos maintain proper layout while loading
|
|
*/
|
|
.notion-asset-wrapper > div:has(> video) {
|
|
min-height: 200px; /* Fallback minimum height while video loads */
|
|
}
|
|
|
|
/* Once video has loaded and has natural dimensions, allow it to use those */
|
|
.notion-asset-wrapper video[src] {
|
|
min-height: unset;
|
|
}
|
|
|
|
/* Responsive video handling */
|
|
@media (max-width: 768px) {
|
|
.notion-asset-wrapper video,
|
|
.notion-asset-wrapper iframe {
|
|
max-width: 100%;
|
|
}
|
|
|
|
.notion-asset-wrapper:has(video),
|
|
.notion-asset-wrapper:has(iframe) {
|
|
max-width: calc(100vw - 2rem);
|
|
}
|
|
}
|
|
|
|
.notion-hr {
|
|
border-top: 1px solid var(--fg-color-0);
|
|
}
|
|
|
|
.notion-link {
|
|
display: inline-flex;
|
|
opacity: 1;
|
|
cursor: pointer;
|
|
border-bottom: none;
|
|
text-decoration: underline;
|
|
text-underline-offset: 4px;
|
|
text-decoration-thickness: 1px;
|
|
text-decoration-color: var(--fg-color-1);
|
|
&:hover {
|
|
background: #94949426;
|
|
border-radius: 1px;
|
|
box-shadow: 0 0 0 3px #94949426;
|
|
cursor: pointer !important;
|
|
}
|
|
}
|
|
|
|
.notion-link .notion-page-title-text {
|
|
border-bottom: 1px solid var(--fg-color-1);
|
|
}
|
|
|
|
.notion-link:has(.notion-page-title-text) {
|
|
border-bottom: none;
|
|
text-decoration: none;
|
|
}
|
|
|
|
/* Ensure the cover wrapper doesn't clip the icon */
|
|
.notion-page-cover-wrapper {
|
|
overflow: visible !important;
|
|
}
|
|
|
|
/* Style page icon like actual Notion - overlapping cover, aligned with content */
|
|
.notion-page-icon-hero {
|
|
margin-top: -70px !important;
|
|
margin-left: 0 !important;
|
|
left: 0 !important;
|
|
justify-content: flex-start !important;
|
|
transform: none !important;
|
|
overflow: visible !important;
|
|
position: relative;
|
|
z-index: 10;
|
|
}
|
|
|
|
.notion-page-icon-hero.notion-page-icon-image {
|
|
overflow: visible !important;
|
|
}
|
|
|
|
/* Only show arrows on page icons within clickable links */
|
|
.notion-link .notion-page-icon-inline,
|
|
.notion-link .notion-page-icon-span,
|
|
.notion-page-link .notion-page-icon-inline,
|
|
.notion-page-link .notion-page-icon-span {
|
|
position: relative;
|
|
}
|
|
|
|
.notion-link .notion-page-icon-inline::after,
|
|
.notion-link .notion-page-icon-span::after,
|
|
.notion-page-link .notion-page-icon-inline::after,
|
|
.notion-page-link .notion-page-icon-span::after {
|
|
content: "";
|
|
position: absolute;
|
|
right: -0.2em;
|
|
bottom: 0;
|
|
width: 0.7em;
|
|
height: 0.7em;
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 13 13' fill='none'%3E%3Cpath d='M6.30826 4.43292L1.76184 8.98454C1.76176 8.98462 1.76169 8.9847 1.76161 8.98477C1.76158 8.9848 1.76156 8.98482 1.76154 8.98484C1.46068 9.28584 1.25 9.6914 1.25 10.1565C1.25 10.6117 1.45865 11.0119 1.73417 11.2886C2.01014 11.5658 2.41107 11.7773 2.87078 11.7773C3.34169 11.7773 3.73758 11.5617 4.03477 11.2733L4.03482 11.2734L4.04244 11.2657L8.58864 6.72474V8.667C8.58864 9.51956 9.22729 10.2935 10.1521 10.2935C11.0528 10.2935 11.75 9.54534 11.75 8.66127V2.92671C11.75 2.48722 11.5981 2.06381 11.2838 1.74808C10.9689 1.43182 10.5446 1.27728 10.1006 1.27728H4.36028C3.46161 1.27728 2.72804 1.97749 2.72804 2.86942C2.72804 3.79734 3.51104 4.43292 4.35455 4.43292H6.30826Z' fill='%233E3C38' stroke='white' stroke-width='1.5'/%3E%3C/svg%3E");
|
|
background-repeat: no-repeat;
|
|
background-size: contain;
|
|
}
|
|
|
|
.notion-toggle:has(.notion-h1) {
|
|
margin-top: 2rem;
|
|
}
|
|
|
|
/* Fix column layout to be evenly spaced */
|
|
.notion-row .notion-column {
|
|
flex: 1 1 0;
|
|
min-width: 0;
|
|
}
|
|
|
|
/* Ensure assets in columns take full width of their column */
|
|
.notion-column .notion-asset-wrapper {
|
|
width: 100% !important;
|
|
max-width: 100%;
|
|
}
|
|
|
|
/* Fix video sizing within columns */
|
|
.notion-column .notion-asset-wrapper video {
|
|
width: 100%;
|
|
height: auto;
|
|
}
|
|
|
|
/* Fix iframe sizing within columns */
|
|
.notion-column .notion-asset-wrapper iframe {
|
|
width: 100%;
|
|
}
|
|
|
|
/* Ensure the inner div of asset wrapper in columns expands properly */
|
|
.notion-column .notion-asset-wrapper > div {
|
|
width: 100% !important;
|
|
}
|
|
|
|
/* Images in columns should also take full width */
|
|
.notion-column .notion-asset-wrapper img {
|
|
width: 100%;
|
|
height: auto;
|
|
object-fit: contain;
|
|
}
|
|
|
|
/* Ensure notion-row displays as proper flexbox */
|
|
.notion-row {
|
|
display: flex !important;
|
|
flex-wrap: nowrap;
|
|
width: 100%;
|
|
gap: 0;
|
|
}
|
|
|
|
/* Responsive: stack columns on small screens */
|
|
@media (max-width: 640px) {
|
|
.notion-row {
|
|
flex-wrap: wrap;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.notion-row > .notion-column {
|
|
width: 100% !important;
|
|
flex: 1 1 100% !important;
|
|
}
|
|
}
|
|
|
|
/* Override any inline width styles on asset wrappers in columns */
|
|
.notion-column .notion-asset-wrapper[style*="width"] {
|
|
width: 100% !important;
|
|
}
|
|
|
|
/* Handle asset wrapper inner positioning div */
|
|
.notion-column .notion-asset-wrapper > div[style] {
|
|
width: 100% !important;
|
|
max-width: 100% !important;
|
|
}
|
|
|
|
/* Ensure lazy image wrappers in columns expand properly */
|
|
.notion-column .lazy-image-wrapper {
|
|
width: 100% !important;
|
|
}
|