Enable customization of user permissions to interact with annotations in the PDF file, completely client-side with three different levels of permissions:
This demo lets you:
Implementation steps
To add annotations permissions capability to a PDF with WebViewer:
Step 1: Choose your preferred web stack
Step 2: Add the ES6 JavaScript sample code provided in this guide
Once you generate your license key, it will automatically be included in your sample code below.
Apryse collects some data regarding your usage of the SDK for product improvement.
The data that Apryse collects include:
For clarity, no other data is collected by the SDK and Apryse has no access to the contents of your documents.
If you wish to continue without data collection, contact us and we will email you a no-tracking trial key for you to get started.
1
2/* ES6 Compliant Syntax */
3/* GitHub Copilot v1.0, Claude 3.5 Sonnet, September 1, 2025 */
4/* File: index.js */
5
6import WebViewer from '@pdftron/webviewer';
7
8// Annotation Permission section
9//
10// Code to customize user permissions, completely client-side with 3
11// different levels of permissions: administrator, user, and read-only
12//
13
14// Default Document
15const defaultDoc = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/WebviewerDemoDoc.pdf';
16
17// Set default user
18let currentUser = 'Justin';
19
20// List of users with permissions
21let userList = {
22 Justin: { permissions: 'administrator', canView: true, hidden: [] },
23 Sally: { permissions: 'user', canView: true, hidden: [] },
24 Brian: { permissions: 'read-only', canView: true, hidden: [] }
25};
26
27// Annotation types that can be toggled for visibility
28let toggleableTypes = [];
29
30// Customize UI
31const customizeUI = async (instance) => {
32 const { Annotations } = instance.Core;
33
34 // Load default document
35 await instance.Core.documentViewer.loadDocument(defaultDoc, {
36 extension: 'pdf',
37 });
38 instance.UI.setToolbarGroup('toolbarGroup-Annotate', true);
39
40 // Add sticky note, free hand, and highlight to toggleable types
41 toggleableTypes = [
42 {
43 displayName: 'Sticky notes',
44 annotationType: Annotations.StickyAnnotation,
45 },
46 {
47 displayName: 'Free hand',
48 annotationType: Annotations.FreeHandAnnotation,
49 },
50 {
51 displayName: 'Highlight',
52 annotationType: Annotations.TextHighlightAnnotation,
53 },
54 ];
55
56 // Set user data for mentions in notes tool
57 const userData = Object.keys(userList).map((user) => ({
58 value: user,
59 email: `${user.toLowerCase()}@pdftron.com`,
60 }));
61 instance.UI.mentions.setUserData(userData);
62
63 // Set default user in the WebViewer
64 setUser(instance, currentUser);
65};
66
67// Set selected user in the WebViewer
68const setUser = (instance, username) => {
69 const UI = instance.UI;
70 const { annotationManager }= instance.Core;
71 annotationManager.setCurrentUser(username);
72 const permissions = userList[username].permissions;
73
74 if (permissions === 'administrator') {
75 annotationManager.promoteUserToAdmin();
76 UI.disableViewOnlyMode();
77 } else if (permissions === 'read-only') {
78 UI.enableViewOnlyMode();
79 annotationManager.demoteUserFromAdmin();
80 } else {
81 UI.disableViewOnlyMode();
82 annotationManager.demoteUserFromAdmin();
83 }
84
85 currentUser = username;
86 setAnnotationsForUser(instance);
87 updateUIControls();
88};
89
90// Set annotations for current user
91const setAnnotationsForUser = (instance) => {
92 const { annotationManager } = instance.Core;
93
94 const { hidden } = userList[currentUser];
95
96 // First get a list of all the types that should be hidden
97 const hiddenTypeMap = toggleableTypes.reduce((acc, type) => {
98 if (hidden.indexOf(type.displayName) > -1) {
99 acc.push(type.annotationType);
100 }
101 return acc;
102 }, []);
103
104 const allAnnots = annotationManager.getAnnotationsList();
105 const toShow = [];
106 const toHide = [];
107
108 // Generate lists of annotations to show and hide
109 allAnnots.forEach((annot) => {
110 const isType = hiddenTypeMap.some((type) => annot instanceof type);
111 if (isType) {
112 toHide.push(annot);
113 } else {
114 toShow.push(annot);
115 }
116 });
117
118 // Show and hide annotations
119 annotationManager.showAnnotations(toShow);
120 annotationManager.hideAnnotations(toHide);
121};
122
123// Add user to user list
124const addUser = (instance, name, p) => {
125 userList = {
126 ...userList,
127 [name]: { permissions: p, canView: true, hidden: [] },
128 };
129};
130
131// Toggle annotation type visibility for current user
132const toggleAnnotations = (instance, type) => {
133 const { displayName } = type;
134 const { hidden } = userList[currentUser];
135 const idx = hidden.indexOf(displayName);
136 const newArray = hidden.slice(0);
137
138 if (idx !== -1) {
139 newArray.splice(idx, 1);
140 } else {
141 newArray.push(displayName);
142 }
143
144 userList = {
145 ...userList,
146 [currentUser]: {
147 ...userList[currentUser],
148 hidden: newArray
149 }
150 };
151
152 setAnnotationsForUser(instance);
153 updateUIControls();
154};
155
156// Check if annotation type is visible for current user
157const isChecked = (type) => {
158 if (!currentUser) return;
159 const { displayName } = type;
160 const user = currentUser;
161 return userList[user].hidden.indexOf(displayName) === -1;
162};
163
164
165// Helper functions for configuration snippet modal
166const perm = () => {
167 return currentUser ? userList[currentUser].permissions : null;
168}
169const hiddenList = () => {
170 return currentUser ? userList[currentUser].hidden : [];
171};
172
173let text = '';
174let annotText = '';
175
176const setText = () => {
177 const permission = perm();
178 if (permission === 'administrator') {
179 text = 'annotationManager.promoteUserToAdmin()';
180 } else if (permission === 'read-only') {
181 text = 'UI.enableViewOnlyMode()';
182 } else {
183 text = 'annotationManager.demoteUserFromAdmin();\n annotationManager.disableReadOnlyMode();';
184 }
185
186 return text;
187};
188
189const setAnnotText = () => {
190 const list = hiddenList();
191 if (list.length === 0) {
192 annotText = `
193 annotationManager.showAnnotations(allAnnots);
194 `;
195 } else {
196 let ifStatement = list.reduce((acc, hidden) => {
197 if (hidden === 'Sticky notes') {
198 acc += ' annot instanceof Annotations.StickyAnnotation || \n';
199 }
200 if (hidden === 'Free hand') {
201 acc += ' annot instanceof Annotations.FreeHandAnnotation || \n';
202 }
203 if (hidden === 'Highlight') {
204 acc += ' annot instanceof Annotations.TextHighlightAnnotation || \n';
205 }
206
207 return acc;
208 }, '');
209
210 ifStatement = ifStatement.substring(8, ifStatement.length - 5);
211
212 annotText = `
213 const hideList = allAnnots.filter(annot => {
214 return ${ifStatement};
215 });
216 annotationManager.hideAnnotations(hideList);
217 `;
218 }
219
220 return annotText;
221};
222
223// WebViewer section
224//
225// This code initializes the WebViewer with the basic settings
226// that are found in the default showcase WebViewer
227//
228
229const searchParams = new URLSearchParams(window.location.search);
230const history = window.history || window.parent.history || window.top.history;
231const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
232const element = document.getElementById('viewer');
233
234// Initialize WebViewer with the specified settings
235WebViewer({
236 path: '/lib',
237 licenseKey: licenseKey,
238 enableFilePicker: true,
239}, element).then((instance) => {
240 // Enable the measurement toolbar so it appears with all the other tools, and disable Cloudy rectangular tool
241 const cloudyTools = [
242 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT,
243 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT2,
244 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT3,
245 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT4,
246 ];
247 instance.UI.enableFeatures([instance.UI.Feature.Measurement, instance.UI.Feature.Initials]);
248 instance.UI.disableTools(cloudyTools);
249
250 // Set default toolbar group to Annotate
251 instance.UI.setToolbarGroup('toolbarGroup-Annotate');
252
253 // Set default tool on mobile devices to Pan.
254 // https://apryse.atlassian.net/browse/WVR-3134
255 if (isMobileDevice()) {
256 instance.UI.setToolMode(instance.Core.Tools.ToolNames.PAN);
257 }
258
259 instance.Core.documentViewer.addEventListener('documentUnloaded', () => {
260 if (searchParams.has('file')) {
261 searchParams.delete('file');
262 history.replaceState(null, '', '?' + searchParams.toString());
263 }
264 });
265
266 instance.Core.annotationManager.enableAnnotationNumbering();
267
268 instance.UI.NotesPanel.enableAttachmentPreview();
269
270 // Add the demo-specific functionality
271 customizeUI(instance).then(() => {
272 // Create UI controls after demo is initialized
273 createUIControls(instance);
274 });
275});
276
277// Function to check if the user is on a mobile device
278const isMobileDevice = () => {
279 return (
280 navigator.userAgentData?.mobile ??
281 /android|iphone|ipad|ipod|blackberry|iemobile|opera mini|mobile/i.test(
282 window.navigator.userAgent
283 )
284 );
285}
286
287// Cleanup function for when the demo is closed or page is unloaded
288const cleanup = (instance) => {
289 const { documentViewer } = instance.Core;
290
291 if (typeof instance !== 'undefined' && instance.UI) {
292
293 // Reset annotation user to default 'Guest' and promote to admin
294 const annotationManager = documentViewer.getAnnotationManager();
295 annotationManager.setCurrentUser('Guest');
296 annotationManager.promoteUserToAdmin();
297 annotationManager.disableReadOnlyMode();
298
299 console.log('Cleaning up compare-files demo');
300 }
301};
302
303// Register cleanup for page unload
304window.addEventListener('beforeunload', () => cleanup());
305window.addEventListener('unload', () => cleanup());
306
307
308// UI section
309//
310// Helper code to add controls to the viewer holding the buttons
311// This code creates a container for the buttons, styles them, and adds them to the viewer
312//
313
314// User selection
315const userPageSection = (instance) => {
316 // Create a wrapper div for the user section
317 const wrapper = document.createElement('div');
318 wrapper.className = 'user-page-section';
319
320 // Select User label
321 const selectUserLabel = document.createElement('h2');
322 selectUserLabel.className = 'header select-user-header';
323 selectUserLabel.textContent = 'Select User';
324
325 wrapper.appendChild(selectUserLabel);
326
327 // Users buttons container
328 const buttonsContainer = document.createElement('div');
329 buttonsContainer.className = 'buttons-container';
330
331 Object.keys(userList).forEach((username) => {
332 const button = document.createElement('button');
333 button.className = 'btn btn-user';
334 button.textContent = `${username} (${userList[username].permissions})`;
335 button.onclick = () => {
336 setUser(instance, username);
337 };
338
339 buttonsContainer.appendChild(button);
340 });
341 wrapper.appendChild(buttonsContainer);
342
343 // Add User clickable label
344 const addUserLabel = document.createElement('label');
345 addUserLabel.className = 'add-user-label';
346 addUserLabel.textContent = 'Add user';
347 addUserLabel.onclick = () => {
348 const addUserContainer = document.querySelector('.add-user-container');
349 if (addUserContainer.classList.contains('visible')) {
350 addUserContainer.classList.remove('visible');
351 addUserLabel.textContent = 'Add user';
352 } else {
353 addUserContainer.classList.add('visible');
354 addUserLabel.textContent = 'Close';
355 }
356 };
357
358 wrapper.appendChild(addUserLabel);
359
360 // Add User container
361 const addUserContainer = document.createElement('div');
362 addUserContainer.className = 'add-user-container';
363
364 // Add User input field
365 const input = document.createElement('input');
366 input.type = 'text';
367 input.placeholder = 'Username';
368 input.className = 'input add-user-input';
369
370 addUserContainer.appendChild(input);
371
372 // Add User permission dropdown
373 const permission = document.createElement('select');
374 permission.className = 'input add-user-permission';
375 permission.options.add(new Option('Administrator', 'administrator'));
376 permission.options.add(new Option('User', 'user'));
377 permission.options.add(new Option('Read-Only', 'read-only'));
378
379 addUserContainer.appendChild(permission);
380
381 // Add User add button
382 const addButton = document.createElement('button');
383 addButton.className = 'btn btn-submit-user';
384 addButton.textContent = 'Add';
385 addButton.onclick = () => {
386 // Validate input
387 const name = input.value;
388 if (name === '') return;
389 const p = permission.value;
390
391 // Add user to user list
392 addUser(instance, name, p);
393
394 // Add button for new user
395 const buttonsContainer = document.querySelector('.buttons-container');
396 const button = document.createElement('button');
397 button.className = 'btn btn-user';
398 button.textContent = `${name} (${p})`;
399 button.onclick = () => {
400 setUser(instance, name);
401 };
402 buttonsContainer.appendChild(button);
403
404 // Reset input fields
405 input.value = '';
406 permission.options.selectedIndex = 0;
407
408 // Close add user section
409 addUserLabel.click();
410 };
411
412 addUserContainer.appendChild(addButton);
413
414 wrapper.appendChild(addUserContainer);
415
416 return wrapper;
417};
418
419// Role permissions description
420const rolePermissionsPageSection = () => {
421 const wrapper = document.createElement('div');
422 wrapper.className = 'role-permissions-section';
423
424 const rolePermissionsLabel = document.createElement('h2');
425 rolePermissionsLabel.className = 'header role-permissions-header';
426 rolePermissionsLabel.textContent = 'Role Permissions';
427
428 wrapper.appendChild(rolePermissionsLabel);
429
430 const rolePermissionDescription = document.createElement('p');
431 rolePermissionDescription.className = 'text role-permission-paragraph';
432 const permission = perm();
433 if (permission === 'administrator') {
434 rolePermissionDescription.innerHTML = '<b> Admin: </b> Can add, edit, or remove any annotations created by anyone';
435 } else if (permission === 'read-only') {
436 rolePermissionDescription.innerHTML = '<b> Read-Only: </b> Can only view annotations';
437 } else { // user
438 rolePermissionDescription.innerHTML = '<b> User: </b> Can create, and edit or remove annotations created by themself';
439 }
440
441 wrapper.appendChild(rolePermissionDescription);
442
443 return wrapper;
444};
445
446// Viewing permissions checkboxes for selected user
447const viewingPermissionsPageSection = (instance) => {
448 const wrapper = document.createElement('div');
449 wrapper.className = 'viewing-permissions-section';
450
451 // Viewing Permissions label
452 const viewingPermissionsLabel = document.createElement('label');
453 viewingPermissionsLabel.className = 'header viewing-permissions-label';
454 viewingPermissionsLabel.textContent = `Set viewing permissions for ` + (currentUser ? currentUser : '...');
455
456 wrapper.appendChild(viewingPermissionsLabel);
457
458 // Viewing Permissions for each annotation type
459 const checkboxContainer = document.createElement('div');
460 checkboxContainer.className = 'checkbox-container';
461
462 toggleableTypes.forEach((type) => {
463 const checkboxRow = document.createElement('div');
464
465 const checkbox = document.createElement('input');
466 checkbox.type = 'checkbox';
467 checkbox.id = `view-${type.displayName}-checkbox`;
468 checkbox.checked = isChecked(type);
469 checkbox.onchange = () => {
470 toggleAnnotations(instance, type);
471 };
472
473 checkboxRow.appendChild(checkbox);
474
475 const label = document.createElement('label');
476 label.className = 'text checkbox-label';
477 label.ariaLabel = `Toggle ${type.displayName} annotations`;
478 label.textContent = `${type.displayName}`;
479
480 checkboxRow.appendChild(label);
481 checkboxContainer.appendChild(checkboxRow);
482 });
483 wrapper.appendChild(checkboxContainer);
484
485 return wrapper;
486};
487
488
489// Helper function to create UI controls
490const createUIControls = (instance) => {
491 // Create a container for all controls (label, dropdown, and buttons)
492 const controlsContainer = document.createElement('div');
493 controlsContainer.className = 'controls-container';
494
495 // Add user section
496 controlsContainer.appendChild(userPageSection(instance));
497
498 // Add role permissions and viewing permissions sections side by side
499 const roleViewingPermissionsContainer = document.createElement('div');
500 roleViewingPermissionsContainer.className = 'role-viewing-permissions-container';
501 roleViewingPermissionsContainer.appendChild(rolePermissionsPageSection());
502 roleViewingPermissionsContainer.appendChild(viewingPermissionsPageSection(instance));
503 controlsContainer.appendChild(roleViewingPermissionsContainer);
504
505
506
507 element.insertBefore(controlsContainer, element.firstChild);
508};
509
510// Helper function to update UI controls
511const updateUIControls = () => {
512 // Update role permission description
513 const rolePermissionDescription = document.querySelector('.role-permission-paragraph');
514 if (rolePermissionDescription) {
515 const permission = perm();
516 if (permission === 'administrator') {
517 rolePermissionDescription.innerHTML = '<b> Admin: </b> Can add, edit, or remove any annotations created by anyone';
518 } else if (permission === 'read-only') {
519 rolePermissionDescription.innerHTML = '<b> Read-Only: </b> Can only view annotations';
520 } else { // user
521 rolePermissionDescription.innerHTML = '<b> User: </b> Can create, and edit or remove annotations created by themself';
522 }
523 }
524
525 // Update viewing permissions label
526 const viewingPermissionsLabel = document.querySelector('.viewing-permissions-label');
527 if (viewingPermissionsLabel) {
528 viewingPermissionsLabel.textContent = `Set viewing permissions for ` + (currentUser ? currentUser : '...');
529 }
530
531 // Update checkboxes
532 toggleableTypes.forEach((type) => {
533 const checkbox = document.getElementById(`view-${type.displayName}-checkbox`);
534 if (checkbox) {
535 checkbox.checked = isChecked(type);
536 }
537 });
538
539 // Update configuration snippet text
540 const codeBlock = document.getElementById('config-snippet-code-block');
541 if (codeBlock) {
542 codeBlock.textContent = `const wvElement = document.getElementById('viewer');
543WebViewer({ ...options }, wvElement)
544.then(instance => {
545 const { annotationManager } = instance.Core;
546 annotationManager.setCurrentUser('${currentUser}');
547 ${setText()}
548 const allAnnots = annotationManager.getAnnotationsList();
549 ${setAnnotText()}
550})`;
551 }
552};
1/* CSS Standard Compliant Syntax */
2/* GitHub Copilot v1.0, Claude 3.5 Sonnet, September 1, 2025 */
3/* File: index.css */
4
5/* Base Classes */
6.btn {
7 display: inline-flex;
8 position: relative;
9 outline: 2px solid transparent;
10 outline-offset: 2px;
11 border-radius: 4px;
12 font-size: 14px;
13 font-weight: 600;
14 cursor: pointer;
15 transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;
16 transition-duration: 200ms;
17 text-decoration: none;
18 line-height: 100%;
19 user-select: none;
20}
21
22.input {
23 outline: 2px solid transparent;
24 outline-offset: 2px;
25 position: relative;
26 transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;
27 transition-duration: 200ms;
28 border-radius: 4px;
29 color: #485056;
30 border-width: 1px;
31 border-style: solid;
32 border-color: #cfd4da;
33 box-sizing: border-box;
34 font-size: 14px;
35 height: 25px;
36 cursor: pointer;
37}
38
39.header {
40 font-weight: bold;
41 font-size: 14px;
42 line-height: 125%;
43 color: #334250;
44 padding-bottom: 10px;
45 display: flex;
46 justify-content: space-between;
47}
48
49.text {
50 font-size: 14px;
51 line-height: 20px;
52 color: #485056;
53 letter-spacing: -0.3px;
54}
55
56/* Controls Container */
57.controls-container {
58 display: flex;
59 flex-direction: column;
60 gap: 16px;
61 margin: 5px 0;
62 padding-bottom: 5px;
63 border-bottom: 1px solid #e0e0e0;
64 background-color: rgba(112, 198, 255, 0.2);
65}
66
67/* User Page Section */
68.user-page-section {
69 margin-bottom: 8px;
70}
71
72/* Headers */
73.select-user-header,
74.role-permissions-header {
75 margin: 0;
76}
77
78/* Buttons Container */
79.buttons-container {
80 display: flex;
81 flex-direction: row;
82 gap: 10px;
83}
84
85/* User Buttons */
86.btn-user {
87 white-space: nowrap;
88 width: 100%;
89 border: 0;
90 padding-inline: 20px;
91 padding-top: 12px;
92 padding-bottom: 12px;
93 height: 40px;
94 min-width: 40px;
95 background-color: #0056b3;
96 color: #ffffff;
97 margin-top: 5px;
98 max-width: fit-content;
99}
100
101.btn-user:hover {
102 background-color: #004494;
103 transform: translateY(-1px);
104 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
105}
106
107/* Add User Label */
108.add-user-label {
109 font-weight: bold;
110 font-size: 14px;
111 line-height: 125%;
112 color: #0206a8;
113 cursor: pointer;
114}
115
116/* Add User Container */
117.add-user-container {
118 display: none; /* Initially hidden */
119 gap: 3px;
120 align-items: flex-start;
121 margin-top: 10px;
122}
123
124.add-user-container.visible {
125 display: flex;
126}
127
128/* Add User Input */
129.add-user-input {
130 width: 200px;
131 appearance: none;
132 border-radius: 6px;
133 background-color: #ffffff;
134 padding-inline-start: 16px;
135 padding-inline-end: 16px;
136}
137
138/* Add User Permission Dropdown */
139.add-user-permission {
140 padding-inline-end: 24px;
141 padding-bottom: 1px;
142 line-height: normal;
143 background-color: #f0f0f0;
144 margin-left: 3px;
145}
146
147/* Submit Button */
148.btn-submit-user {
149 appearance: none;
150 align-items: center;
151 justify-content: center;
152 padding-top: 12px;
153 padding-bottom: 12px;
154 height: 25px;
155 min-width: 32px;
156 background-color: #ffffff;
157 color: #0056b3;
158 white-space: nowrap;
159 padding-inline: 20px;
160 border-style: solid;
161 border-width: 1px;
162 border-color: #0056b3;
163 margin-left: 3px;
164}
165
166.btn-submit-user:hover {
167 background-color: #0056b3;
168 color: #ffffff;
169}
170/* Role Permissions Section */
171.role-permissions-section {
172 margin-bottom: 8px;
173 width: 50%;
174}
175
176.role-permission-paragraph {
177 margin: 0;
178}
179
180/* Viewing Permissions Section */
181.viewing-permissions-section {
182 display: flex;
183 flex-direction: column;
184}
185
186/* Checkbox Container */
187.checkbox-container {
188 display: flex;
189 flex-direction: row;
190 gap: 8px;
191}
192
193.checkbox-container > div {
194 display: flex;
195 align-items: center;
196 gap: 8px;
197}
198
199/* Checkboxes */
200.checkbox-container input[type="checkbox"] {
201 align-items: center;
202 justify-content: center;
203 width: 20px;
204 transition-property: box-shadow;
205 transition-duration: 200ms;
206 border-width: 2px;
207 border-style: solid;
208 border-radius: 4px;
209 border-color: inherit;
210 height: 20px;
211 display: inline-flex;
212 user-select: none;
213 flex-shrink: 0;
214}
215
216/* Checkbox Labels */
217.checkbox-label {
218 align-content: center;
219 justify-content: center;
220 cursor: pointer;
221}
222
223/* Role and Viewing Permissions Container */
224.role-viewing-permissions-container {
225 display: flex;
226 flex-direction: row;
227 gap: 20px;
228}
229
230/* Responsive Design */
231@media (max-width: 768px) {
232 .buttons-container {
233 flex-direction: column;
234 align-items: flex-start;
235 }
236
237 .btn-user {
238 width: 100%;
239 max-width: 100%;
240 margin: 5px 0;
241 }
242
243 .add-user-container {
244 flex-direction: column;
245 align-items: stretch;
246 }
247
248 .input.add-user-input,
249 .input.add-user-permission {
250 width: 100%;
251 margin-left: 0;
252 margin-bottom: 8px;
253 }
254
255 .btn.btn-submit-user {
256 margin-left: 0;
257 margin-top: 8px;
258 }
259
260 .role-viewing-permissions-container {
261 flex-direction: column;
262 gap: 16px;
263 }
264}
265
266/* Modal Styles */
267.config-modal {
268 display: none;
269 position: fixed;
270 z-index: 1000;
271 left: 0;
272 top: 0;
273 width: 100%;
274 height: 100%;
275 background-color: rgba(0, 0, 0, 0.5);
276}
277
278.config-modal-content {
279 position: fixed;
280 bottom: 0px;
281 left: 0px;
282 right: 0px;
283 max-width: 100vw;
284 transform: translateX(0px) translateY(0px) translateZ(0px);
285 display: flex;
286 flex-direction: column;
287 width: 100%;
288 outline: transparent solid 2px;
289 outline-offset: 2px;
290 z-index: 9999999999999;
291 max-height: 100vh;
292 background-color: #2d2d2d;
293 box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
294 pointer-events: auto;
295 font-size: 14px;
296}
297
298.config-modal-header {
299 display: flex;
300 flex: 0 1 0%;
301 padding-inline-start: 24px;
302 padding-inline-end: 24px;
303 padding-top: 12px;
304 padding-bottom: 0px;
305 font-size: 16px;
306 font-weight: 600;
307 color: #ffffff;
308 vertical-align: middle;
309}
310
311.btn-copy-config {
312 width: 20px;
313 height: 20px;
314 display: inline-block;
315 line-height: 1em;
316 flex-shrink: 0;
317 color: currentColor;
318 vertical-align: middle;
319 margin-left: 15px;
320 margin-bottom: 2px;
321 cursor: pointer;
322 background: none;
323 border: none;
324 font-size: 24px;
325}
326
327.btn-close-config {
328 background: none;
329 border: none;
330 outline: transparent solid 2px;
331 outline-offset: 2px;
332 display: flex;
333 align-items: center;
334 justify-content: center;
335 flex-shrink: 0;
336 width: 32px;
337 height: 32px;
338 border-radius: 6px;
339 transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;
340 transition-duration: 200ms;
341 font-size: 24px;
342 position: absolute;
343 top: 8px;
344 right: 12px;
345 color: #ffffff;
346 cursor: pointer;
347}
348
349.btn-copy-config:hover,
350.btn-close-config:hover {
351 color: #c7d2dd;
352}
353
354.config-snippet-code-pre {
355 color: #cccccc;
356 background: #2d2d2d;
357 font-family: monospace;
358 font-size: 1em;
359 text-align: left;
360 white-space: pre;
361 word-spacing: normal;
362 word-break: normal;
363 overflow-wrap: normal;
364 line-height: 1.5;
365 tab-size: 4;
366 hyphens: none;
367 padding: 1em;
368 margin: 0.5em 0px;
369 overflow: auto;
370 padding-inline-start: 32px;
371 padding-inline-end: 32px;
372 padding-top: 0px;
373 padding-bottom: 0px;
374}
375
376.btn-open-config {
377 display: inline-flex;
378 appearance: none;
379 align-items: center;
380 justify-content: center;
381 user-select: none;
382 position: relative;
383 white-space: nowrap;
384 vertical-align: middle;
385 outline: 2px solid transparent;
386 outline-offset: 2px;
387 width: 100%;
388 line-height: 100%;
389 border-radius: 4px;
390 font-size: 16px;
391 font-weight: 600;
392 transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;
393 transition-duration: 200ms;
394 cursor: pointer;
395 text-decoration: none;
396 padding-inline: 20px 20px;
397 padding-top: 12px;
398 padding-bottom: 12px;
399 height: 40px;
400 background-color: #ffffff;
401 color: #0056b3;
402 border-style: solid;
403 border-width: 1px;
404 border-color: #0056b3;
405 margin-left: 3px;
406}
407
408.btn-open-config:hover {
409 background-color: #0056b3;
410 color: #ffffff;
411}
Did you find this helpful?
Trial setup questions?
Ask experts on DiscordNeed other help?
Contact SupportPricing or product questions?
Contact Sales