🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

beblob

Package Overview
Dependencies
Maintainers
2
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

beblob - npm Package Compare versions

Comparing version
2.0.0
to
2.1.0
demo/beblob.js

Sorry, the diff of this file is too big to display

+773
/* ===========================
CSS CUSTOM PROPERTIES (Theming)
Override these variables to create custom themes.
=========================== */
/* Default: Light theme */
.beblob-widget {
--beblob-bg: #e8e6e3;
--beblob-text: #2c2c2c;
--beblob-text-muted: #777;
--beblob-accent: #d4cfc9;
--beblob-border: #d0cec9;
--beblob-link: #5a7de0;
--beblob-comment-bg: #f0eeed;
--beblob-comment-border: #d8d5d0;
--beblob-button-bg: transparent;
--beblob-button-text: #2c2c2c;
--beblob-button-border: #c4c1bc;
--beblob-button-hover-bg: rgba(0, 0, 0, 0.06);
--beblob-tab-bg: transparent;
--beblob-tab-active-bg: #d4cfc9;
--beblob-tab-active-text: #2c2c2c;
--beblob-reaction-bg: transparent;
--beblob-reaction-border: #c4c1bc;
--beblob-reaction-hover-bg: rgba(0, 0, 0, 0.06);
--beblob-popup-bg: #ddd9d4;
--beblob-popup-border: #c4c1bc;
--beblob-editor-bg: #f0eeed;
--beblob-editor-border: #d0cec9;
--beblob-editor-text: #2c2c2c;
--beblob-editor-placeholder: #999;
--beblob-disabled-bg: #d5d3d1;
--beblob-disabled-text: #aaa;
--beblob-disabled-border: #ccc;
--beblob-issues-bg: transparent;
--beblob-shadow: rgba(0, 0, 0, 0.06);
--beblob-cursor: #2c2c2c;
--beblob-font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--beblob-radius: 6px;
--beblob-spinner-track: #d5d3d1;
--beblob-spinner-head: #999;
--beblob-toolbar-btn-bg: transparent;
--beblob-toolbar-btn-border: transparent;
--beblob-toolbar-btn-hover-bg: rgba(0, 0, 0, 0.08);
--beblob-toolbar-border: #d0cec9;
}
/* --- Dark Theme --- */
.beblob-widget.theme-dark {
--beblob-bg: #1d1f21;
--beblob-text: #c9cacc;
--beblob-text-muted: #888;
--beblob-accent: #2a2d30;
--beblob-border: #383b40;
--beblob-link: #4cd69c;
--beblob-comment-bg: #25282c;
--beblob-comment-border: #383b40;
--beblob-button-bg: transparent;
--beblob-button-text: #c9cacc;
--beblob-button-border: #484d54;
--beblob-button-hover-bg: rgba(255, 255, 255, 0.08);
--beblob-tab-bg: transparent;
--beblob-tab-active-bg: #2bbc8a;
--beblob-tab-active-text: #1d1f21;
--beblob-reaction-bg: transparent;
--beblob-reaction-border: #484d54;
--beblob-reaction-hover-bg: rgba(255, 255, 255, 0.08);
--beblob-popup-bg: #2a2d30;
--beblob-popup-border: #484d54;
--beblob-editor-bg: #25282c;
--beblob-editor-border: #383b40;
--beblob-editor-text: #c9cacc;
--beblob-editor-placeholder: #666;
--beblob-disabled-bg: #2a2c2e;
--beblob-disabled-text: #555;
--beblob-disabled-border: #383b40;
--beblob-issues-bg: transparent;
--beblob-shadow: rgba(0, 0, 0, 0.3);
--beblob-cursor: #c9cacc;
--beblob-spinner-track: #333;
--beblob-spinner-head: #2bbc8a;
--beblob-toolbar-btn-bg: transparent;
--beblob-toolbar-btn-border: transparent;
--beblob-toolbar-btn-hover-bg: rgba(255, 255, 255, 0.1);
--beblob-toolbar-border: #383b40;
}
/* --- White Theme --- */
.beblob-widget.theme-white {
--beblob-bg: #ffffff;
--beblob-text: #24292e;
--beblob-text-muted: #888;
--beblob-accent: #f6f8fa;
--beblob-border: #e1e4e8;
--beblob-link: #0366d6;
--beblob-comment-bg: #fafbfc;
--beblob-comment-border: #e1e4e8;
--beblob-button-bg: transparent;
--beblob-button-text: #24292e;
--beblob-button-border: #d1d5da;
--beblob-button-hover-bg: rgba(0, 0, 0, 0.04);
--beblob-tab-bg: transparent;
--beblob-tab-active-bg: #e1e4e8;
--beblob-tab-active-text: #24292e;
--beblob-reaction-bg: transparent;
--beblob-reaction-border: #d1d5da;
--beblob-reaction-hover-bg: rgba(0, 0, 0, 0.04);
--beblob-popup-bg: #f6f8fa;
--beblob-popup-border: #d1d5da;
--beblob-editor-bg: #fafbfc;
--beblob-editor-border: #e1e4e8;
--beblob-editor-text: #24292e;
--beblob-editor-placeholder: #999;
--beblob-disabled-bg: #f6f8fa;
--beblob-disabled-text: #aaa;
--beblob-disabled-border: #e1e4e8;
--beblob-issues-bg: transparent;
--beblob-shadow: rgba(0, 0, 0, 0.04);
--beblob-cursor: #24292e;
--beblob-spinner-track: #eee;
--beblob-spinner-head: #999;
--beblob-toolbar-btn-bg: transparent;
--beblob-toolbar-btn-border: transparent;
--beblob-toolbar-btn-hover-bg: rgba(0, 0, 0, 0.06);
--beblob-toolbar-border: #e1e4e8;
}
/* --- Light Theme (explicit, same as default) --- */
.beblob-widget.theme-light {
/* Uses default variables */
}
/* --- Classic Theme --- */
.beblob-widget.theme-classic {
--beblob-bg: #fafafa;
--beblob-text: #111;
--beblob-text-muted: #888;
--beblob-accent: #f0f0f0;
--beblob-border: #ddd;
--beblob-link: #0366d6;
--beblob-comment-bg: #fff;
--beblob-comment-border: #ddd;
--beblob-button-bg: transparent;
--beblob-button-text: #111;
--beblob-button-border: #ccc;
--beblob-button-hover-bg: rgba(0, 0, 0, 0.05);
--beblob-tab-bg: transparent;
--beblob-tab-active-bg: #e0e0e0;
--beblob-tab-active-text: #111;
--beblob-reaction-bg: transparent;
--beblob-reaction-border: #ccc;
--beblob-reaction-hover-bg: rgba(0, 0, 0, 0.05);
--beblob-popup-bg: #eee;
--beblob-popup-border: #ccc;
--beblob-editor-bg: #fff;
--beblob-editor-border: #ddd;
--beblob-editor-text: #111;
--beblob-editor-placeholder: #999;
--beblob-disabled-bg: #eee;
--beblob-disabled-text: #aaa;
--beblob-disabled-border: #ccc;
--beblob-issues-bg: transparent;
--beblob-shadow: rgba(0, 0, 0, 0.05);
--beblob-cursor: #111;
--beblob-spinner-track: #eee;
--beblob-spinner-head: #999;
--beblob-toolbar-btn-bg: transparent;
--beblob-toolbar-btn-border: transparent;
--beblob-toolbar-btn-hover-bg: rgba(0, 0, 0, 0.06);
--beblob-toolbar-border: #ddd;
}
/* ===========================
BASE STYLES
=========================== */
.beblob-widget {
position: relative;
min-height: 120px;
background-color: var(--beblob-bg);
color: var(--beblob-text);
font-family: var(--beblob-font-family);
border-radius: var(--beblob-radius);
padding: 20px;
transition: background-color 0.2s ease, color 0.2s ease;
}
.beblob-widget *,
.beblob-widget *::before,
.beblob-widget *::after {
box-sizing: border-box;
}
.beblob-widget a {
color: var(--beblob-link);
transition: color 0.15s ease;
}
.beblob-widget a:hover {
opacity: 0.8;
}
/* ===========================
ISSUE CONTAINER
=========================== */
.issue {
margin-bottom: 0;
border: none;
padding: 0;
}
#issuesContainer {
padding: 0;
border-radius: 0;
margin-bottom: 4px;
border: none;
background-color: var(--beblob-issues-bg);
color: var(--beblob-text);
}
/* ===========================
COMMENTS
=========================== */
.comment {
margin-bottom: 12px;
padding: 14px 16px;
border: 1px solid var(--beblob-comment-border);
border-radius: var(--beblob-radius);
background-color: var(--beblob-comment-bg);
color: var(--beblob-text);
transition: background-color 0.2s ease, border-color 0.2s ease;
}
.comment-header {
display: flex;
align-items: center;
margin-bottom: 10px;
padding-bottom: 8px;
border-bottom: 1px solid var(--beblob-border);
flex-wrap: wrap;
gap: 6px;
}
.comment-header img {
width: 28px;
height: 28px;
border-radius: 50%;
flex-shrink: 0;
}
.comment-header .author-info {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 4px;
font-size: 0.85rem;
}
.comment-header .author-info .author-name {
font-weight: 600;
color: var(--beblob-link);
text-decoration: none;
}
.comment-header .author-info .author-name:hover {
text-decoration: underline;
}
.comment-header .author-info .commented-on,
.comment-header .author-info .comment-timestamp {
color: var(--beblob-text-muted);
font-size: 0.8rem;
}
.comment-body {
line-height: 1.6;
font-size: 0.92rem;
}
.comment-body pre {
background-color: var(--beblob-accent);
padding: 12px;
border-radius: var(--beblob-radius);
overflow-x: auto;
border: 1px solid var(--beblob-border);
font-size: 0.85rem;
}
.comment-body code {
font-size: 0.85em;
}
.comment-body p:first-child {
margin-top: 0;
}
.comment-body p:last-child {
margin-bottom: 0;
}
.comments-label {
margin-bottom: 10px;
font-size: 0.8rem;
font-weight: 500;
color: var(--beblob-text-muted);
}
.comments-label a {
color: var(--beblob-text-muted);
text-decoration: none;
}
.comments-label a:hover {
text-decoration: underline;
}
/* No comments empty state */
.beblob-empty-state {
text-align: center;
padding: 24px 16px;
color: var(--beblob-text-muted);
font-size: 0.9rem;
}
/* ===========================
COMMENT TEXTAREA & EDITOR
=========================== */
.comment-textarea-container {
margin-top: 16px;
border-top: 1px solid var(--beblob-border);
padding-top: 16px;
position: relative;
}
.comment-textarea-container textarea {
width: 100%;
min-height: 80px;
padding: 10px;
border: 1px solid var(--beblob-editor-border);
border-radius: var(--beblob-radius);
margin-bottom: 8px;
resize: vertical;
background-color: var(--beblob-editor-bg);
color: var(--beblob-editor-text);
font-family: var(--beblob-font-family);
font-size: 0.9rem;
}
/* ===========================
CURRENT USER AVATAR
=========================== */
.tab .current-user-avatar-container {
margin-left: auto;
display: flex;
align-items: center;
}
.tab .current-user-avatar-container img {
width: 24px;
height: 24px;
border-radius: 50%;
}
/* ===========================
BUTTONS
=========================== */
.gl-button,
.gl-button.btn-block {
display: inline-flex;
justify-content: center;
align-items: center;
padding: 8px 16px;
background-color: var(--beblob-button-bg);
color: var(--beblob-button-text);
border: 1px solid var(--beblob-button-border);
font-size: 0.8rem;
font-family: var(--beblob-font-family);
border-radius: var(--beblob-radius);
cursor: pointer;
transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
font-weight: 500;
}
.gl-button:hover,
.gl-button.btn-block:hover {
background-color: var(--beblob-button-hover-bg);
}
.gl-button:focus-visible {
outline: 2px solid var(--beblob-link);
outline-offset: 2px;
}
/* Tab container */
.tab {
display: flex;
align-items: center;
border-bottom: 1px solid var(--beblob-border);
margin-bottom: 0;
gap: 2px;
}
/* Tab buttons — NOT .gl-button style */
.tab .tablinks {
background-color: var(--beblob-tab-bg);
color: var(--beblob-text-muted);
border: none;
border-top-left-radius: var(--beblob-radius);
border-top-right-radius: var(--beblob-radius);
padding: 8px 16px;
cursor: pointer;
font-family: var(--beblob-font-family);
font-size: 0.8rem;
font-weight: 500;
transition: background-color 0.15s ease, color 0.15s ease;
/* Reset .gl-button styles that tabs inherit */
box-shadow: none;
}
.tab .tablinks:hover {
background-color: var(--beblob-button-hover-bg);
color: var(--beblob-text);
}
.tab .tablinks:focus-visible {
outline: 2px solid var(--beblob-link);
outline-offset: -2px;
}
.tab .tablinks.active {
background-color: var(--beblob-tab-active-bg);
color: var(--beblob-tab-active-text);
border-bottom: 1px solid transparent;
font-weight: 600;
}
/* Add Comment Button */
#addCommentButton {
padding: 8px 16px;
border-radius: var(--beblob-radius);
cursor: pointer;
transition: background-color 0.15s ease, color 0.15s ease;
border: 1px solid var(--beblob-button-border);
background-color: var(--beblob-button-bg);
color: var(--beblob-button-text);
font-family: var(--beblob-font-family);
font-size: 0.8rem;
font-weight: 500;
}
#addCommentButton:hover {
background-color: var(--beblob-button-hover-bg);
}
#addCommentButton:disabled {
background-color: var(--beblob-disabled-bg);
color: var(--beblob-disabled-text);
border-color: var(--beblob-disabled-border);
cursor: not-allowed;
}
/* Auth Button */
.auth-button {
gap: 6px;
}
.auth-button img.gitlab-logo {
width: 18px;
height: 18px;
}
/* ===========================
REACTIONS & POPUP
=========================== */
.reactions-center {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 6px;
margin-bottom: 8px;
}
.reaction-btn,
.reaction-toggle-btn,
.reaction-popup-btn {
display: inline-flex;
justify-content: center;
align-items: center;
min-height: 34px;
min-width: 34px;
padding: 4px 8px;
border: 1px solid var(--beblob-reaction-border);
background-color: var(--beblob-reaction-bg);
color: var(--beblob-text);
cursor: pointer;
font-size: 0.9em;
border-radius: var(--beblob-radius);
transition: background-color 0.15s ease, border-color 0.15s ease;
}
.reaction-btn:hover,
.reaction-toggle-btn:hover,
.reaction-popup-btn:hover {
background-color: var(--beblob-reaction-hover-bg);
}
.reaction-btn:focus-visible,
.reaction-toggle-btn:focus-visible,
.reaction-popup-btn:focus-visible {
outline: 2px solid var(--beblob-link);
outline-offset: 2px;
}
.reaction-toggle-btn svg {
width: 16px;
height: 16px;
fill: currentColor;
}
.reaction-container {
position: relative;
display: inline-block;
}
#reaction-popup {
position: absolute;
top: 100%;
left: 0;
padding: 4px;
z-index: 1000;
border-radius: var(--beblob-radius);
background-color: var(--beblob-popup-bg);
border: 1px solid var(--beblob-popup-border);
color: var(--beblob-text);
display: none;
white-space: nowrap;
box-shadow: 0 4px 12px var(--beblob-shadow);
}
#reaction-popup button {
display: inline-flex;
margin: 2px;
}
/* ===========================
COMMENT ACTIONS
=========================== */
.comment-actions {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 10px;
gap: 8px;
}
/* ===========================
LOADING INDICATOR
=========================== */
.loading-indicator {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
text-align: center;
font-size: 0.85rem;
color: var(--beblob-text-muted);
}
.spinner {
display: inline-block;
border: 2px solid var(--beblob-spinner-track);
border-top: 2px solid var(--beblob-spinner-head);
border-radius: 50%;
width: 24px;
height: 24px;
animation: beblob-spin 0.8s linear infinite;
margin: 0 auto 8px auto;
}
@keyframes beblob-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* ===========================
EDITOR TOOLBAR (EasyMDE/CodeMirror)
=========================== */
.beblob-widget .editor-toolbar {
border: none;
border-bottom: 1px solid var(--beblob-toolbar-border);
background-color: transparent;
border-radius: 0;
padding: 4px 2px;
opacity: 0.7;
}
.beblob-widget .editor-toolbar:hover {
opacity: 1;
}
.beblob-widget.theme-dark .editor-toolbar {
opacity: 1;
}
.beblob-widget .editor-toolbar a,
.beblob-widget .editor-toolbar button {
display: inline-flex;
justify-content: center;
align-items: center;
text-decoration: none !important;
width: 28px;
height: 28px;
margin: 0 1px;
border-radius: 4px;
cursor: pointer;
background-color: var(--beblob-toolbar-btn-bg);
color: var(--beblob-text) !important;
border: 1px solid var(--beblob-toolbar-btn-border);
transition: background-color 0.15s ease;
}
.beblob-widget .editor-toolbar a:hover,
.beblob-widget .editor-toolbar button:hover {
background-color: var(--beblob-toolbar-btn-hover-bg) !important;
}
.beblob-widget .editor-toolbar button.active {
background-color: var(--beblob-toolbar-btn-hover-bg) !important;
border-color: var(--beblob-toolbar-btn-border) !important;
}
.beblob-widget .editor-toolbar i.separator {
border-left-color: var(--beblob-border);
}
/* CodeMirror (Markdown editor) */
.beblob-widget #Preview,
.beblob-widget .CodeMirror {
background-color: var(--beblob-editor-bg);
border: 1px solid var(--beblob-editor-border);
color: var(--beblob-editor-text);
border-radius: 0 0 var(--beblob-radius) var(--beblob-radius);
transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
min-height: 80px;
font-size: 0.9rem;
}
.beblob-widget .CodeMirror pre,
.beblob-widget .CodeMirror-line {
color: inherit;
}
.beblob-widget .CodeMirror-placeholder {
color: var(--beblob-editor-placeholder);
}
.beblob-widget .CodeMirror-cursor {
border-left: 1px solid var(--beblob-cursor) !important;
}
/* EasyMDE container overrides */
.beblob-widget .EasyMDEContainer {
border: 1px solid var(--beblob-editor-border);
border-radius: var(--beblob-radius);
overflow: hidden;
}
.beblob-widget .EasyMDEContainer .CodeMirror {
border: none;
border-radius: 0;
}
.beblob-widget .EasyMDEContainer .editor-toolbar {
border-bottom: 1px solid var(--beblob-editor-border);
}
/* Hide the EasyMDE status bar */
.beblob-widget .editor-statusbar {
display: none;
}
/* Dark theme: code block and syntax highlighting overrides */
.beblob-widget.theme-dark .comment-body pre {
background-color: var(--beblob-accent);
color: var(--beblob-text);
}
.beblob-widget.theme-dark .hljs {
background: var(--beblob-accent);
color: var(--beblob-text);
}
.beblob-widget.theme-dark .cm-s-default .cm-header { color: #7ec8e3; }
.beblob-widget.theme-dark .cm-s-default .cm-quote { color: #6bbd6b; }
.beblob-widget.theme-dark .cm-s-default .cm-comment { color: #888; }
.beblob-widget.theme-dark .cm-s-default .cm-string { color: #e09070; }
.beblob-widget.theme-dark .cm-s-default .cm-keyword { color: #c678dd; }
/* ===========================
MOBILE RESPONSIVE
=========================== */
@media (max-width: 600px) {
.beblob-widget {
padding: 12px;
}
/* Stack comment header vertically on small screens */
.comment-header {
flex-direction: column;
align-items: flex-start;
gap: 4px;
}
/* Touch-friendly buttons (min 44px tap target) */
.gl-button,
.gl-button.btn-block,
.reaction-btn,
.reaction-toggle-btn,
.reaction-popup-btn {
min-height: 44px;
min-width: 44px;
padding: 10px 16px;
}
/* Stack comment actions vertically */
.comment-actions {
flex-direction: column;
gap: 8px;
}
.comment-actions .gl-button,
.comment-actions .auth-button,
.comment-actions #addCommentButton {
width: 100%;
justify-content: center;
}
/* Tab buttons take equal space */
.tab {
flex-wrap: wrap;
}
.tab .tablinks {
flex: 1;
text-align: center;
padding: 10px 12px;
}
/* Editor toolbar scrolls horizontally */
.beblob-widget .editor-toolbar {
overflow-x: auto;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
}
/* Reaction popup positioned better on mobile */
#reaction-popup {
left: auto;
right: 0;
}
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 380 380"><defs><style>.cls-1{fill:#e24329;}.cls-2{fill:#fc6d26;}.cls-3{fill:#fca326;}</style></defs><g id="LOGO"><path class="cls-1" d="M282.83,170.73l-.27-.69-26.14-68.22a6.81,6.81,0,0,0-2.69-3.24,7,7,0,0,0-8,.43,7,7,0,0,0-2.32,3.52l-17.65,54H154.29l-17.65-54A6.86,6.86,0,0,0,134.32,99a7,7,0,0,0-8-.43,6.87,6.87,0,0,0-2.69,3.24L97.44,170l-.26.69a48.54,48.54,0,0,0,16.1,56.1l.09.07.24.17,39.82,29.82,19.7,14.91,12,9.06a8.07,8.07,0,0,0,9.76,0l12-9.06,19.7-14.91,40.06-30,.1-.08A48.56,48.56,0,0,0,282.83,170.73Z"/><path class="cls-2" d="M282.83,170.73l-.27-.69a88.3,88.3,0,0,0-35.15,15.8L190,229.25c19.55,14.79,36.57,27.64,36.57,27.64l40.06-30,.1-.08A48.56,48.56,0,0,0,282.83,170.73Z"/><path class="cls-3" d="M153.43,256.89l19.7,14.91,12,9.06a8.07,8.07,0,0,0,9.76,0l12-9.06,19.7-14.91S209.55,244,190,229.25C170.45,244,153.43,256.89,153.43,256.89Z"/><path class="cls-2" d="M132.58,185.84A88.19,88.19,0,0,0,97.44,170l-.26.69a48.54,48.54,0,0,0,16.1,56.1l.09.07.24.17,39.82,29.82s17-12.85,36.57-27.64Z"/></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 380 380"><defs><style>.cls-1{fill:#171321;}</style></defs><g id="LOGO"><path class="cls-1" d="M282.83,170.73l-.27-.69-26.14-68.22a6.81,6.81,0,0,0-2.69-3.24,7,7,0,0,0-8,.43,7,7,0,0,0-2.32,3.52l-17.65,54H154.29l-17.65-54A6.86,6.86,0,0,0,134.32,99a7,7,0,0,0-8-.43,6.87,6.87,0,0,0-2.69,3.24L97.44,170l-.26.69a48.54,48.54,0,0,0,16.1,56.1l.09.07.24.17,39.82,29.82,19.7,14.91,12,9.06a8.07,8.07,0,0,0,9.76,0l12-9.06,19.7-14.91,40.06-30,.1-.08A48.56,48.56,0,0,0,282.83,170.73Z"/></g></svg>
/**
* Returns the GitLab API v4 base URL for the configured instance.
* @param {object} config - Widget configuration with gitlabUrl
* @returns {string}
*/
export function apiBaseUrl(config) {
return `${config.gitlabUrl}/api/v4`;
}
/**
* Returns the GitLab OAuth base URL for the configured instance.
* @param {object} config - Widget configuration with gitlabUrl
* @returns {string}
*/
export function oauthBaseUrl(config) {
return `${config.gitlabUrl}/oauth`;
}
import { describe, it, expect } from 'vitest';
import { apiBaseUrl, oauthBaseUrl } from '../../src/js/api/gitlab-url.js';
describe('gitlab-url helpers', () => {
describe('apiBaseUrl', () => {
it('returns default gitlab.com API URL', () => {
expect(apiBaseUrl({ gitlabUrl: 'https://gitlab.com' })).toBe(
'https://gitlab.com/api/v4'
);
});
it('returns custom instance API URL', () => {
expect(apiBaseUrl({ gitlabUrl: 'https://gitlab.example.com' })).toBe(
'https://gitlab.example.com/api/v4'
);
});
});
describe('oauthBaseUrl', () => {
it('returns default gitlab.com OAuth URL', () => {
expect(oauthBaseUrl({ gitlabUrl: 'https://gitlab.com' })).toBe(
'https://gitlab.com/oauth'
);
});
it('returns custom instance OAuth URL', () => {
expect(oauthBaseUrl({ gitlabUrl: 'https://gitlab.example.com' })).toBe(
'https://gitlab.example.com/oauth'
);
});
});
});
+0
-2

@@ -68,5 +68,3 @@ image: node:20

script:
- test -n "$NPM_TOKEN" && echo "NPM_TOKEN is set" || echo "NPM_TOKEN is EMPTY"
- echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
- npm whoami || echo "npm whoami failed"
- npm publish

@@ -73,0 +71,0 @@ rules:

@@ -106,2 +106,3 @@ <!DOCTYPE html>

data-theme="light"
<!-- data-gitlab-url="https://gitlab.example.com" -->
defer>

@@ -108,0 +109,0 @@ </script>

@@ -612,4 +612,8 @@ /* ===========================

}
.beblob-widget.theme-dark .editor-toolbar {
opacity: 1;
}
.beblob-widget .editor-toolbar a {
.beblob-widget .editor-toolbar a,
.beblob-widget .editor-toolbar button {
display: inline-flex;

@@ -630,6 +634,12 @@ justify-content: center;

.beblob-widget .editor-toolbar a:hover {
background-color: var(--beblob-toolbar-btn-hover-bg);
.beblob-widget .editor-toolbar a:hover,
.beblob-widget .editor-toolbar button:hover {
background-color: var(--beblob-toolbar-btn-hover-bg) !important;
}
.beblob-widget .editor-toolbar button.active {
background-color: var(--beblob-toolbar-btn-hover-bg) !important;
border-color: var(--beblob-toolbar-btn-border) !important;
}
.beblob-widget .editor-toolbar i.separator {

@@ -685,2 +695,19 @@ border-left-color: var(--beblob-border);

/* Dark theme: code block and syntax highlighting overrides */
.beblob-widget.theme-dark .comment-body pre {
background-color: var(--beblob-accent);
color: var(--beblob-text);
}
.beblob-widget.theme-dark .hljs {
background: var(--beblob-accent);
color: var(--beblob-text);
}
.beblob-widget.theme-dark .cm-s-default .cm-header { color: #7ec8e3; }
.beblob-widget.theme-dark .cm-s-default .cm-quote { color: #6bbd6b; }
.beblob-widget.theme-dark .cm-s-default .cm-comment { color: #888; }
.beblob-widget.theme-dark .cm-s-default .cm-string { color: #e09070; }
.beblob-widget.theme-dark .cm-s-default .cm-keyword { color: #c678dd; }
/* ===========================

@@ -687,0 +714,0 @@ MOBILE RESPONSIVE

{
"name": "beblob",
"version": "2.0.0",
"version": "2.1.0",
"description": "This is an open-source widget allowing bloggers to insert comment section to their blogs hosted on GitLab Pages. The comments use GitLab issues underneath.",

@@ -14,3 +14,3 @@ "main": "dist/beblob.js",

"test:coverage": "vitest run --coverage",
"demo": "npx http-server demo -p 9090 -o"
"demo": "npm run build && cp dist/beblob.js demo/beblob.js && cp -r dist/css dist/images demo/ && npx http-server demo -p 9090 -o"
},

@@ -17,0 +17,0 @@ "repository": {

@@ -58,2 +58,3 @@ # BeBlob

| `theme` | No | Built-in theme: `dark`, `white`, `light` (default), `classic` |
| `gitlabUrl` | No | GitLab instance URL (default: `https://gitlab.com`). Use for self-managed GitLab. |

@@ -60,0 +61,0 @@ ## Themes

@@ -612,4 +612,8 @@ /* ===========================

}
.beblob-widget.theme-dark .editor-toolbar {
opacity: 1;
}
.beblob-widget .editor-toolbar a {
.beblob-widget .editor-toolbar a,
.beblob-widget .editor-toolbar button {
display: inline-flex;

@@ -630,6 +634,12 @@ justify-content: center;

.beblob-widget .editor-toolbar a:hover {
background-color: var(--beblob-toolbar-btn-hover-bg);
.beblob-widget .editor-toolbar a:hover,
.beblob-widget .editor-toolbar button:hover {
background-color: var(--beblob-toolbar-btn-hover-bg) !important;
}
.beblob-widget .editor-toolbar button.active {
background-color: var(--beblob-toolbar-btn-hover-bg) !important;
border-color: var(--beblob-toolbar-btn-border) !important;
}
.beblob-widget .editor-toolbar i.separator {

@@ -685,2 +695,19 @@ border-left-color: var(--beblob-border);

/* Dark theme: code block and syntax highlighting overrides */
.beblob-widget.theme-dark .comment-body pre {
background-color: var(--beblob-accent);
color: var(--beblob-text);
}
.beblob-widget.theme-dark .hljs {
background: var(--beblob-accent);
color: var(--beblob-text);
}
.beblob-widget.theme-dark .cm-s-default .cm-header { color: #7ec8e3; }
.beblob-widget.theme-dark .cm-s-default .cm-quote { color: #6bbd6b; }
.beblob-widget.theme-dark .cm-s-default .cm-comment { color: #888; }
.beblob-widget.theme-dark .cm-s-default .cm-string { color: #e09070; }
.beblob-widget.theme-dark .cm-s-default .cm-keyword { color: #c678dd; }
/* ===========================

@@ -687,0 +714,0 @@ MOBILE RESPONSIVE

import { state } from '../state.js';
import { apiBaseUrl } from './gitlab-url.js';

@@ -7,7 +8,8 @@ /**

* @param {string} commentBody - The comment text
* @param {object} config - Widget configuration
* @returns {object|null} The created note or null on failure
*/
export async function addCommentToIssue(accessToken, commentBody) {
export async function addCommentToIssue(accessToken, commentBody, config) {
try {
const apiUrl = `https://gitlab.com/api/v4/projects/${state.projectId}/issues/${state.currentIssueId}/notes`;
const apiUrl = `${apiBaseUrl(config)}/projects/${state.projectId}/issues/${state.currentIssueId}/notes`;
const response = await fetch(apiUrl, {

@@ -14,0 +16,0 @@ method: 'POST',

import { state } from '../state.js';
import { IssueFetchStrategy } from '../constants.js';
import { apiBaseUrl } from './gitlab-url.js';

@@ -9,7 +10,8 @@ /**

* @param {string} [description]
* @param {object} config - Widget configuration
* @returns {object|null} The created issue or null on failure
*/
export async function createIssue(accessToken, title, description) {
export async function createIssue(accessToken, title, description, config) {
try {
const apiUrl = `https://gitlab.com/api/v4/projects/${state.projectId}/issues`;
const apiUrl = `${apiBaseUrl(config)}/projects/${state.projectId}/issues`;
const response = await fetch(apiUrl, {

@@ -38,7 +40,14 @@ method: 'POST',

* @param {string} fetchParam - URL, page title, or issue ID
* @param {object} config - Widget configuration
* @returns {object|null} The found issue or null
*/
export async function fetchGitLabIssue(accessToken, fetchType, fetchParam) {
export async function fetchGitLabIssue(
accessToken,
fetchType,
fetchParam,
config
) {
try {
let apiUrl = `https://gitlab.com/api/v4/projects/${state.projectId}/issues`;
const base = apiBaseUrl(config);
let apiUrl = `${base}/projects/${state.projectId}/issues`;
if (fetchType === IssueFetchStrategy.URL) {

@@ -49,3 +58,3 @@ apiUrl += `?search=${encodeURIComponent(fetchParam)}`;

} else if (fetchType === IssueFetchStrategy.ISSUE_ID) {
apiUrl = `https://gitlab.com/api/v4/projects/${state.projectId}/issues/${fetchParam}`;
apiUrl = `${base}/projects/${state.projectId}/issues/${fetchParam}`;
}

@@ -82,8 +91,9 @@

* @param {number} issueIid
* @param {object} config - Widget configuration
* @returns {Array} Array of discussion objects
*/
export async function fetchIssueDiscussions(accessToken, issueIid) {
export async function fetchIssueDiscussions(accessToken, issueIid, config) {
try {
const response = await fetch(
`https://gitlab.com/api/v4/projects/${state.projectId}/issues/${issueIid}/discussions`,
`${apiBaseUrl(config)}/projects/${state.projectId}/issues/${issueIid}/discussions`,
{ headers: { Authorization: `Bearer ${accessToken}` } }

@@ -90,0 +100,0 @@ );

import { state } from '../state.js';
import { apiBaseUrl } from './gitlab-url.js';

@@ -8,7 +9,8 @@ /**

* @param {string} projectName
* @param {object} config - Widget configuration
* @returns {number|null} The project ID or null on failure
*/
export async function fetchProjectId(accessToken, projectName) {
export async function fetchProjectId(accessToken, projectName, config) {
try {
const url = `https://gitlab.com/api/v4/projects?search=${encodeURIComponent(projectName)}`;
const url = `${apiBaseUrl(config)}/projects?search=${encodeURIComponent(projectName)}`;
const response = await fetch(url, {

@@ -15,0 +17,0 @@ headers: { Authorization: `Bearer ${accessToken}` },

import { state } from '../state.js';
import { apiBaseUrl } from './gitlab-url.js';

@@ -7,7 +8,8 @@ /**

* @param {number} issueIid
* @param {object} config - Widget configuration
* @returns {Array} Array of reaction objects
*/
export async function fetchReactions(accessToken, issueIid) {
export async function fetchReactions(accessToken, issueIid, config) {
try {
const apiUrl = `https://gitlab.com/api/v4/projects/${state.projectId}/issues/${issueIid}/award_emoji`;
const apiUrl = `${apiBaseUrl(config)}/projects/${state.projectId}/issues/${issueIid}/award_emoji`;
const response = await fetch(apiUrl, {

@@ -31,7 +33,8 @@ headers: { Authorization: `Bearer ${accessToken}` },

* @param {string} reactionName
* @param {object} config - Widget configuration
* @returns {object} The created reaction
* @throws {Error} on failure (including 'already been taken')
*/
export async function addReaction(accessToken, issueIid, reactionName) {
const apiUrl = `https://gitlab.com/api/v4/projects/${state.projectId}/issues/${issueIid}/award_emoji`;
export async function addReaction(accessToken, issueIid, reactionName, config) {
const apiUrl = `${apiBaseUrl(config)}/projects/${state.projectId}/issues/${issueIid}/award_emoji`;
const response = await fetch(apiUrl, {

@@ -56,7 +59,8 @@ method: 'POST',

* @param {number} awardId
* @param {object} config - Widget configuration
* @returns {boolean} true on success
*/
export async function removeReaction(accessToken, issueIid, awardId) {
export async function removeReaction(accessToken, issueIid, awardId, config) {
try {
const apiUrl = `https://gitlab.com/api/v4/projects/${state.projectId}/issues/${issueIid}/award_emoji/${awardId}`;
const apiUrl = `${apiBaseUrl(config)}/projects/${state.projectId}/issues/${issueIid}/award_emoji/${awardId}`;
const response = await fetch(apiUrl, {

@@ -82,10 +86,11 @@ method: 'DELETE',

* @param {number} issueIid
* @param {object} config - Widget configuration
* @returns {Array} Updated reactions array
*/
export async function toggleReaction(name, accessToken, issueIid) {
export async function toggleReaction(name, accessToken, issueIid, config) {
if (!state.currentUser) {
return await fetchReactions(accessToken, issueIid);
return await fetchReactions(accessToken, issueIid, config);
}
const reactions = await fetchReactions(accessToken, issueIid);
const reactions = await fetchReactions(accessToken, issueIid, config);
const existing = reactions.find(

@@ -96,9 +101,9 @@ (r) => r.name === name && r.user && r.user.id === state.currentUser.id

if (existing) {
await removeReaction(accessToken, issueIid, existing.id);
await removeReaction(accessToken, issueIid, existing.id, config);
} else {
try {
await addReaction(accessToken, issueIid, name);
await addReaction(accessToken, issueIid, name, config);
} catch (err) {
if (err.message.indexOf('already been taken') !== -1) {
const refreshed = await fetchReactions(accessToken, issueIid);
const refreshed = await fetchReactions(accessToken, issueIid, config);
const dup = refreshed.find(

@@ -108,3 +113,3 @@ (r) => r.name === name && r.user && r.user.id === state.currentUser.id

if (dup) {
await removeReaction(accessToken, issueIid, dup.id);
await removeReaction(accessToken, issueIid, dup.id, config);
}

@@ -117,3 +122,3 @@ } else {

return await fetchReactions(accessToken, issueIid);
return await fetchReactions(accessToken, issueIid, config);
}
import { state } from '../state.js';
import { apiBaseUrl } from './gitlab-url.js';

@@ -7,7 +8,8 @@ /**

* @param {string} accessToken
* @param {object} config - Widget configuration
* @returns {object|null} The user object or null on failure
*/
export async function fetchCurrentUser(accessToken) {
export async function fetchCurrentUser(accessToken, config) {
try {
const response = await fetch('https://gitlab.com/api/v4/user', {
const response = await fetch(`${apiBaseUrl(config)}/user`, {
headers: { Authorization: `Bearer ${accessToken}` },

@@ -14,0 +16,0 @@ });

import { LOCAL_STORAGE_KEY } from './constants.js';
import { getAssetUrl } from './css-loader.js';
import { oauthBaseUrl } from './api/gitlab-url.js';

@@ -11,3 +12,3 @@ /**

const state = encodeURIComponent(originalUrl);
const oauthUrl = `https://gitlab.com/oauth/authorize?client_id=${config.clientId}&redirect_uri=${encodeURIComponent(config.redirectUri)}&response_type=code&state=${state}`;
const oauthUrl = `${oauthBaseUrl(config)}/authorize?client_id=${config.clientId}&redirect_uri=${encodeURIComponent(config.redirectUri)}&response_type=code&state=${state}`;
window.location.href = oauthUrl;

@@ -41,3 +42,3 @@ }

export async function requestAccessToken(code, config) {
const tokenUrl = 'https://gitlab.com/oauth/token';
const tokenUrl = `${oauthBaseUrl(config)}/token`;
const params = {

@@ -44,0 +45,0 @@ client_id: config.clientId,

@@ -22,2 +22,6 @@ /**

theme: script.dataset.theme || 'light',
gitlabUrl: (script.dataset.gitlabUrl || 'https://gitlab.com').replace(
/\/+$/,
''
),
};

@@ -24,0 +28,0 @@

@@ -84,3 +84,3 @@ import EasyMDE from 'easymde';

if (!storedToken || !state.projectId || !state.currentIssueId) return;
await addCommentToIssue(storedToken, comment);
await addCommentToIssue(storedToken, comment, config);
editor.value('');

@@ -132,3 +132,7 @@ await fetchAndDisplayIssue(storedToken, config, markedInstance);

async function loadProjectAndIssue(accessToken, config, markedInstance) {
const projectId = await fetchProjectId(accessToken, config.projectName);
const projectId = await fetchProjectId(
accessToken,
config.projectName,
config
);
if (!projectId) {

@@ -141,3 +145,3 @@ localStorage.removeItem(LOCAL_STORAGE_KEY);

await fetchAndDisplayIssue(accessToken, config, markedInstance);
const user = await fetchCurrentUser(accessToken);
const user = await fetchCurrentUser(accessToken, config);
if (user) {

@@ -161,3 +165,4 @@ displayCurrentUserAvatar(user);

IssueFetchStrategy.URL,
window.location.href
window.location.href,
config
);

@@ -168,3 +173,4 @@ } else if (strategy === IssueFetchStrategy.PAGE_TITLE) {

IssueFetchStrategy.PAGE_TITLE,
document.title
document.title,
config
);

@@ -175,3 +181,4 @@ } else if (strategy === IssueFetchStrategy.ISSUE_ID) {

IssueFetchStrategy.ISSUE_ID,
config.issueId
config.issueId,
config
);

@@ -189,3 +196,4 @@ }

document.title,
'Automatically created by BeBlob.'
'Automatically created by BeBlob.',
config
);

@@ -198,3 +206,3 @@ if (issue) {

if (issue) {
await displayIssue(issue, accessToken, markedInstance);
await displayIssue(issue, accessToken, markedInstance, config);
}

@@ -201,0 +209,0 @@ } catch (error) {

@@ -11,4 +11,5 @@ import { renderMarkdown } from '../markdown.js';

* @param {Marked} markedInstance
* @param {object} config - Widget configuration
*/
export async function displayIssue(issue, accessToken, markedInstance) {
export async function displayIssue(issue, accessToken, markedInstance, config) {
const issuesContainer = document.getElementById('issuesContainer');

@@ -36,3 +37,7 @@ if (!issuesContainer) return;

const discussions = await fetchIssueDiscussions(accessToken, issue.iid);
const discussions = await fetchIssueDiscussions(
accessToken,
issue.iid,
config
);
if (Array.isArray(discussions)) {

@@ -62,4 +67,4 @@ discussions.forEach((discussion) => {

const reactions = await fetchReactions(accessToken, issue.iid);
renderReactions(reactions, accessToken, issue.iid);
const reactions = await fetchReactions(accessToken, issue.iid, config);
renderReactions(reactions, accessToken, issue.iid, config);
}

@@ -66,0 +71,0 @@

@@ -10,4 +10,5 @@ import { predefinedReactions, emojiMap } from '../constants.js';

* @param {number} issueIid
* @param {object} config - Widget configuration
*/
export function renderReactions(reactions, accessToken, issueIid) {
export function renderReactions(reactions, accessToken, issueIid, config) {
const container = document.getElementById('reactionsContainer');

@@ -40,3 +41,3 @@ if (!container) return;

issueIid,
reactions
config
);

@@ -55,3 +56,3 @@ centerDiv.appendChild(btn);

issueIid,
reactions
config
);

@@ -92,4 +93,9 @@ centerDiv.appendChild(btn);

btn.addEventListener('click', async () => {
const updated = await toggleReaction(name, accessToken, issueIid);
renderReactions(updated, accessToken, issueIid);
const updated = await toggleReaction(
name,
accessToken,
issueIid,
config
);
renderReactions(updated, accessToken, issueIid, config);
popup.style.display = 'none';

@@ -106,3 +112,3 @@ });

function createReactionButton(name, count, accessToken, issueIid) {
function createReactionButton(name, count, accessToken, issueIid, config) {
const btn = document.createElement('button');

@@ -112,6 +118,6 @@ btn.className = 'reaction-btn';

btn.addEventListener('click', async () => {
const updated = await toggleReaction(name, accessToken, issueIid);
renderReactions(updated, accessToken, issueIid);
const updated = await toggleReaction(name, accessToken, issueIid, config);
renderReactions(updated, accessToken, issueIid, config);
});
return btn;
}
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { state } from '../../src/js/state.js';
const defaultConfig = { gitlabUrl: 'https://gitlab.com' };
describe('api/issues', () => {

@@ -28,3 +30,8 @@ beforeEach(() => {

);
await fetchGitLabIssue('token', 'url', 'https://example.com/page');
await fetchGitLabIssue(
'token',
'url',
'https://example.com/page',
defaultConfig
);

@@ -50,3 +57,8 @@ expect(mockFetch).toHaveBeenCalledWith(

);
await fetchGitLabIssue('token', 'pageTitle', 'My Blog / Post Title');
await fetchGitLabIssue(
'token',
'pageTitle',
'My Blog / Post Title',
defaultConfig
);

@@ -72,3 +84,3 @@ // Note: split('/').pop() keeps the leading space: ' Post Title'

);
await fetchGitLabIssue('token', 'issueId', '42');
await fetchGitLabIssue('token', 'issueId', '42', defaultConfig);

@@ -93,3 +105,8 @@ expect(mockFetch).toHaveBeenCalledWith(

);
const issue = await fetchGitLabIssue('token', 'url', 'https://x.com');
const issue = await fetchGitLabIssue(
'token',
'url',
'https://x.com',
defaultConfig
);

@@ -112,6 +129,31 @@ expect(issue.iid).toBe(99);

);
const result = await fetchGitLabIssue('token', 'url', 'https://x.com');
const result = await fetchGitLabIssue(
'token',
'url',
'https://x.com',
defaultConfig
);
expect(result).toBeNull();
});
it('uses custom gitlabUrl in API calls', async () => {
const mockFetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve([{ iid: 1 }]),
});
vi.stubGlobal('fetch', mockFetch);
const { fetchGitLabIssue } = await import(
'../../src/js/api/issues.js'
);
await fetchGitLabIssue('token', 'url', 'https://x.com', {
gitlabUrl: 'https://gitlab.example.com',
});
expect(mockFetch).toHaveBeenCalledWith(
expect.stringContaining('https://gitlab.example.com/api/v4/'),
expect.any(Object)
);
});
});

@@ -131,3 +173,3 @@

const { createIssue } = await import('../../src/js/api/issues.js');
const result = await createIssue('token', 'New', 'Desc');
const result = await createIssue('token', 'New', 'Desc', defaultConfig);

@@ -144,3 +186,3 @@ expect(result).toEqual(mockIssue);

const { createIssue } = await import('../../src/js/api/issues.js');
const result = await createIssue('token', 'New', 'Desc');
const result = await createIssue('token', 'New', 'Desc', defaultConfig);

@@ -165,3 +207,3 @@ expect(result).toBeNull();

);
const result = await fetchIssueDiscussions('token', 1);
const result = await fetchIssueDiscussions('token', 1, defaultConfig);

@@ -180,3 +222,3 @@ expect(result).toEqual(discussions);

);
const result = await fetchIssueDiscussions('token', 1);
const result = await fetchIssueDiscussions('token', 1, defaultConfig);

@@ -183,0 +225,0 @@ expect(result).toEqual([]);

@@ -41,9 +41,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';

it('stores token on successful exchange', async () => {
vi.stubGlobal(
'fetch',
vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ access_token: 'abc123' }),
})
);
const mockFetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ access_token: 'abc123' }),
});
vi.stubGlobal('fetch', mockFetch);

@@ -54,2 +52,3 @@ const { requestAccessToken } = await import('../src/js/auth.js');

redirectUri: 'https://test.com/',
gitlabUrl: 'https://gitlab.com',
});

@@ -59,2 +58,6 @@

expect(localStorage.getItem(LOCAL_STORAGE_KEY)).toBe('abc123');
expect(mockFetch).toHaveBeenCalledWith(
'https://gitlab.com/oauth/token',
expect.any(Object)
);

@@ -64,2 +67,24 @@ vi.unstubAllGlobals();

it('uses custom gitlabUrl for token exchange', async () => {
const mockFetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ access_token: 'custom-token' }),
});
vi.stubGlobal('fetch', mockFetch);
const { requestAccessToken } = await import('../src/js/auth.js');
await requestAccessToken('auth-code', {
clientId: 'cid',
redirectUri: 'https://test.com/',
gitlabUrl: 'https://gitlab.example.com',
});
expect(mockFetch).toHaveBeenCalledWith(
'https://gitlab.example.com/oauth/token',
expect.any(Object)
);
vi.unstubAllGlobals();
});
it('returns null on failed exchange', async () => {

@@ -75,2 +100,3 @@ vi.stubGlobal(

redirectUri: 'https://test.com/',
gitlabUrl: 'https://gitlab.com',
});

@@ -77,0 +103,0 @@

@@ -86,4 +86,33 @@ import { describe, it, expect, beforeEach } from 'vitest';

expect(config.theme).toBe('dark');
expect(config.gitlabUrl).toBe('https://gitlab.com');
});
it('reads custom gitlabUrl from data attribute', () => {
const script = document.createElement('script');
script.id = 'beblob-script';
script.dataset.clientId = 'my-client';
script.dataset.redirectUri = 'https://test.com/';
script.dataset.projectName = 'my-project';
script.dataset.issueMappingStrategy = 'url';
script.dataset.gitlabUrl = 'https://gitlab.example.com';
document.body.appendChild(script);
const config = readConfig();
expect(config.gitlabUrl).toBe('https://gitlab.example.com');
});
it('strips trailing slashes from gitlabUrl', () => {
const script = document.createElement('script');
script.id = 'beblob-script';
script.dataset.clientId = 'my-client';
script.dataset.redirectUri = 'https://test.com/';
script.dataset.projectName = 'my-project';
script.dataset.issueMappingStrategy = 'url';
script.dataset.gitlabUrl = 'https://gitlab.example.com///';
document.body.appendChild(script);
const config = readConfig();
expect(config.gitlabUrl).toBe('https://gitlab.example.com');
});
it('defaults theme to light', () => {

@@ -90,0 +119,0 @@ const script = document.createElement('script');

Sorry, the diff of this file is too big to display