Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • applications/etuc/hubl
  • applications/hubl
  • decentral1se/hubl
  • rngadam/hubl
  • jvtrudel/hubl
  • 3wc/hubl
6 results
Show changes
Showing
with 774 additions and 111 deletions
{{#if (hasComponent "circles" "projects")}}
<solid-widget name="orbit-menu-publicprivate">
<template>
<div class="${value == 'true' ? 'text-large': 'text-simple-line-icons text-xsmall'}">
${value == 'true' ? '#' : ''}
</div>
</template>
</solid-widget>
{{/if}}
{{#if (hasComponentAll "projects" "admin")}}
<solid-widget name="orbit-project-admins">
<template>
<solid-display
data-src="${value}"
fields="user"
filtered-by="orbit_project_is_admin"
widget-user="orbit-project-captain"
></solid-display>
</template>
</solid-widget>
{{/if}}
{{#if (hasComponentAll "projects" "invoices")}}
<solid-widget name="orbit-project-businessprovider">
<template>
<solid-form class="segment three-quarter sm-full margin-right-medium margin-bottom-smalll"
data-src="${value}"
data-holder=""
naked=""
fields="name, fee"
label-name=""
label-fee=""
class-name="segment two-third padding-right-small sm-padding-none text-small text-semibold text-uppercase text-color-heading"
class-fee="segment third padding-left-small sm-padding-none text-small text-semibold text-uppercase text-color-heading"
data-trans="label-name=project.create.labelBusinessproviderName;label-fee=project.create.labelBusinessproviderFee"
></solid-form>
</template>
</solid-widget>
{{/if}}
{{#if (hasComponentAll "projects" "admin")}}
<solid-widget name="orbit-project-captain">
<template>
<solid-display class="segment margin-top-xxsmall margin-bottom-xxsmall labelled-avatar two-lines block"
data-src="${await value}"
fields="segment1(account.picture), segment2(line1(name), line2(at, username))"
class-segment1="segment"
class-account.picture="avatar"
class-segment2="segment three-quarter margin-left-xsmall"
class-line1="segment block"
class-name="text-small text-semibold text-color-heading text-sub"
class-line2="segment block text-xsmall"
widget-account.picture="orbit-user-avatar"
value-at="@"
></solid-display>
</template>
</solid-widget>
{{/if}}
{{#if (hasComponent "projects")}}
<solid-widget name="orbit-project-edit-admin">
<template>
${value == "true" ? `<div data-trans="circle.list.admin"></div>` : ""}
</template>
</solid-widget>
{{/if}}
{{#if (hasComponent "projects")}}
<solid-widget name="orbit-project-edit-members-delete">
<template>
<solid-ac-checker permission="acl:Delete" data-src="${src}">
<solid-delete
class='segment text-xsmall children-link-text-bold children-link-text-uppercase children-link-button children-link-color-secondary bordered'
data-src="${src}"
data-label=''
data-trans='data-label=project.edit.buttonDelete'
></solid-delete>
</solid-ac-checker>
</template>
</solid-widget>
{{/if}}
{{#if (hasComponent "projects")}}
<solid-widget name="orbit-project-team-contact">
<template>
<solid-link class="icon icon-secondary hover icon-speech margin-left-xsmall margin-right-medium" data-src="${value}" next="{{getRoute 'chat' true}}"></solid-link>
</template>
</solid-widget>
{{/if}}
<solid-widget name='orbit-user-avatar'>
<template>
${value != "" ? `<img src="${value}" />` : `<img src="{{#if @root.client.defaultAvatar}}{{@root.client.defaultAvatar}}{{else}}/images/alien.webp{{/if}}" />`}
</template>
</solid-widget>
{{#if (hasComponent "chat")}}
<solid-widget name="orbit-username-field">
<template>
<label>${label}</label>
<input type="text" title="" pattern="[a-zA-Z0-9]+" label="${label}" data-trans="title=user.create.labelUsernameTitle" name="username" required value="${value}" data-holder>
</template>
</solid-widget>
{{/if}}
import { importCore } from "@helpers/utils";
importCore().then((core) => {
core.Sib.register({
name: "orbit-auto-login",
created() {
if (window.location.pathname === "/login") {
for (const el of document.querySelectorAll(".loggedIn-loader")) {
el.style.display = "flex";
}
window.dispatchEvent(
new CustomEvent("requestNavigation", {
detail: {
route: window.orbit.getRoute(
window.orbit.defaultRoute || "dashboard",
true,
),
},
}),
);
if (!new URLSearchParams(window.location.search).get("code"))
document.querySelector("sib-auth").login();
}
},
});
});
import { LitElement, html, nothing } from "lit";
import { unsafeHTML } from "lit/directives/unsafe-html.js";
import markdownit from "markdown-it";
import mila from "markdown-it-link-attributes";
import { truncate } from "@helpers/utils";
customElements.define(
"orbit-circle-headline",
class OrbitCircleHeadline extends LitElement {
static properties = {
name: { attribute: "name" },
value: { attribute: "value" },
maxlength: { attribute: "maxlength", type: Number },
};
createRenderRoot() {
return this;
}
component = this; // Workaround to trick sib-core that this is a valid widget
render() {
if (!this.name || !this.value) return nothing;
const md = markdownit({
breaks: true,
html: false,
linkify: true,
});
md.use(mila, {
attrs: {
target: "_blank",
rel: "noopener",
},
});
let value = md.render(this.value);
if (this.maxlength > 0) {
const trimmed = this.truncateValue(value, this.maxlength);
if (trimmed.length < value.length) {
value = trimmed.replace(
/<\/p>$/,
' <solid-link bind-resources next="circles-information">(...)</solid-link></p>',
);
}
}
return html`${unsafeHTML(value)}`;
}
/**
* Truncate an HTML string to a given limit (omitting tags).
*
* @param {string} value
* @param {number} limit
* @returns {string}
*/
truncateValue(value, limit) {
const fragment = document.createElement("div");
fragment.innerHTML = value;
truncate(fragment, limit);
return fragment.innerHTML;
}
},
);
import { LitElement, css, html, nothing } from "lit";
// Custom widget for wholesale
customElements.define(
"solid-loader",
class extends LitElement {
static properties = {
position: { attribute: "position" },
hidden: { attribute: "hidden" },
};
static styles = css`
.loader {
margin: 0 auto;
position: relative;
width: 80px;
height: 20px;
}
.loader.loader-top {
top: 30px;
}
.loader div {
position: absolute;
width: 13px;
height: 13px;
border-radius: 50%;
background: var(--color-heading);
animation-timing-function: cubic-bezier(0, 1, 1, 0);
}
.loader.loader-menu div {
width: 8px;
height: 8px;
background: white;
}
.loader div:nth-child(1) {
left: 8px;
animation: lds-ellipsis1 0.6s infinite;
}
.loader div:nth-child(2) {
left: 8px;
animation: lds-ellipsis2 0.6s infinite;
}
.loader div:nth-child(3) {
left: 32px;
animation: lds-ellipsis2 0.6s infinite;
}
.loader div:nth-child(4) {
left: 56px;
animation: lds-ellipsis3 0.6s infinite;
}
@keyframes lds-ellipsis1 {
0% {
transform: scale(0);
}
100% {
transform: scale(1);
}
}
@keyframes lds-ellipsis3 {
0% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}
@keyframes lds-ellipsis2 {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(24px, 0);
}
}
`;
render() {
if (this.hidden) return nothing;
return html`<div class="loader loader-${this.position || "top"}" id="loader-users-create">
<div></div>
<div></div>
<div></div>
<div></div>
</div>`;
}
},
);
import { importCore } from "@helpers/utils";
importCore().then((core) => {
core.Sib.register({
name: "orbit-reactivity",
use: [core.StoreMixin],
attributes: {
targetSrc: {
type: String,
default: "",
callback: function () {
this.subscribe();
},
},
},
async fetchData(value) {
this.resourceId = null;
if (this.nestedField) {
const resource =
core.store.get(value) ||
(await core.store.getData(value, this.context));
const nestedResource = await resource[this.nestedField];
this.resourceId = nestedResource ? nestedResource["@id"] : null;
} else {
this.resourceId = value;
}
await this.replaceAttributesData();
this.subscribe();
},
unsubscribe(resourceId, targetSrc) {
const resourcesSub =
core.store.subscriptionVirtualContainersIndex.get(resourceId);
const targetSub =
core.store.subscriptionVirtualContainersIndex.get(targetSrc);
const newResourceSub = resourcesSub.filter((r) => r !== targetSrc);
const newTargetSub = targetSub.filter((r) => r !== resourceId);
core.store.subscriptionVirtualContainersIndex.set(
resourceId,
newResourceSub,
);
core.store.subscriptionVirtualContainersIndex.set(
targetSrc,
newTargetSub,
);
},
detached() {
this.unsubscribe(this.resourceId, this.targetSrc);
},
subscribe() {
if (
this.oldResourceId &&
this.oldTargetSrc &&
(this.oldResourceId !== this.resourceId ||
this.oldTargetSrc !== this.targetSrc)
) {
this.unsubscribe(this.oldResourceId, this.oldTargetSrc);
}
if (this.resourceId && this.targetSrc) {
core.store.subscribeVirtualContainerTo(this.resourceId, this.targetSrc);
core.store.subscribeVirtualContainerTo(this.targetSrc, this.resourceId);
this.oldResourceId = this.resourceId;
this.oldTargetSrc = this.targetSrc;
}
},
});
});
/**
* Iterates over federations and register them in sibStore if available.
*/
document.addEventListener("DOMContentLoaded", () => {
const feds = [];
for (const [uniq, federation] of Object.entries(federations)) {
if (sibStore && "setLocalData" in sibStore) {
feds.push(sibStore.setLocalData(federation, uniq));
}
}
Promise.all(feds).then(() => {
window.orbit.federationsReady = true;
document.dispatchEvent(new CustomEvent("federations-ready"));
});
});
import {
getComponent,
getComponentFromRoute,
getDefaultRoute,
getRoute,
Swal,
} from "@helpers/utils";
window.orbit = {
...window.orbit,
client,
components,
componentSet: new Set(componentSet),
federations,
federationsReady: false,
npm,
getDefaultRoute,
getComponent,
getComponentFromRoute,
getRoute,
//TODO: Legacies:
defaultRoute: getDefaultRoute(),
Swal,
};
document.addEventListener("federations-ready", () => {
window.orbit.federationsReady = true;
});
// document.addEventListener("DOMContentLoaded", () => {
// if (typeof Sentry !== 'undefined') {
// Sentry.init({
// dsn: "https://TBD@sentry.startinblox.com/TBD",
// environment: document.location.hostname,
// integrations: [
// Sentry.browserTracingIntegration({ tracingOrigins: ["*"] }),
// Sentry.replayIntegration(),
// ],
// tracesSampleRate: 0.2,
// replaysSessionSampleRate: 0.1,
// replaysOnErrorSampleRate: 1.0,
// });
// window.orbit.sentry = Sentry;
// }
// });
document.addEventListener("DOMContentLoaded", function (event) {
document
.querySelector("sib-auth")
.getUser()
.then(user => {
if (user !== null) {
document
.querySelectorAll(".notLoggedIn")
.forEach(el => (el.style.visibility = "visible"));
document
.querySelector('.loggedIn')
.setAttribute("style", "display:none !important");
} else {
document.querySelector('sib-auth').login();
}
});
});
\ No newline at end of file
// auxiliary function closes the user profile menu
function closeUserControls() {
let userControls = document.querySelector("#user-controls");
if (userControls) userControls.removeAttribute("open");
}
function closeLeftMenu() {
let leftMenu = document.querySelector("#main__menu");
if (leftMenu) leftMenu.removeAttribute("open");
}
function closeRightMenu() {
let rightMenu = document.querySelectorAll(".jsRightMenu");
if (Array.from(rightMenu).filter(el => el.hasAttribute("open")).length > 0) {
Array.from(document.querySelectorAll(".views-container")).forEach(vC =>
vC.classList.toggle("sidebar-is-closed")
);
Array.from(rightMenu).forEach(el => el.removeAttribute("open"));
}
}
function openRightMenu() {
let rightMenu = document.querySelectorAll(".jsRightMenu");
Array.from(rightMenu).forEach(el => el.setAttribute("open", ""));
Array.from(document.querySelectorAll(".views-container")).forEach(vC =>
vC.classList.toggle("sidebar-is-closed")
);
}
document.addEventListener("DOMContentLoaded", function(event) {
//- View change event
window.addEventListener("navigate", event => {
closeLeftMenu();
closeUserControls();
});
// Document -> close menu
document.addEventListener("click", event => {
if (!event.target.closest("#user-controls")) {
closeUserControls();
}
if (
!event.target.closest("#main__menu") &&
event.target.id != "toggleMainMenu"
) {
closeLeftMenu();
}
if (
!event.target.className.includes("jsMobileSidebarOpenButton") &&
!event.target.className.includes("jsOffsiteToggle")
) {
closeRightMenu();
}
});
// listen for keypress
document.onkeydown = e => {
e = e || window.event;
if (e.key === "Escape" || e.key === "Esc") {
closeUserControls();
closeLeftMenu();
closeRightMenu();
}
};
document.querySelector("#toggleMainMenu").addEventListener("click", event => {
let leftMenu = document.querySelector("#main__menu");
if (leftMenu.hasAttribute("open")) {
closeLeftMenu();
} else {
leftMenu.setAttribute("open", "");
}
});
const rightMenus = Array.from(document.querySelectorAll("nav.jsRightMenu"));
rightMenus.forEach(rightMenu => {
const btnRightMenu = rightMenu.querySelector("li.jsOffsiteToggle");
btnRightMenu.addEventListener("click", e => {
if (rightMenu.hasAttribute("open")) {
closeRightMenu();
} else {
openRightMenu();
}
});
});
Array.from(document.querySelectorAll(".jsMobileSidebarOpenButton")).forEach(
el => {
el.addEventListener("click", event => {
openRightMenu();
});
}
);
});
\ No newline at end of file
/*
Geocoord helper using Nominatim
Usage:
const madrid = await orbit.geocoord('Madrid');
madrid == ["-3.7035825", "40.4167047"]
*/
window.orbit.geocoord = async (address = false) => {
if (address) {
const nominatim = await fetch(
`https://nominatim.openstreetmap.org/?format=geocodejson&limit=1&q=${encodeURI(address)}`,
);
const response = await nominatim.json();
if (response.features[0]) {
const coords = response.features[0].geometry.coordinates;
if (coords[0] && coords[1]) {
return [String(coords[0]), String(coords[1])];
}
} else {
console.error("Address not found");
}
} else {
console.error("Missing address");
}
return ["-47.15", "-123.716667"];
};
window.orbit.geocalc = (element) => {
const editionForm =
element.parentElement.parentElement.parentElement.parentElement;
window.orbit
.geocoord(
`${editionForm.querySelector('input[name="address_line1"]').value} ${editionForm.querySelector('input[name="address_line2"]').value}`,
)
.then((coords) => {
editionForm.querySelector('input[name="lat"]').value = coords[1];
editionForm.querySelector('input[name="lng"]').value = coords[0];
editionForm.querySelector('input[type="submit"]').click();
});
return false;
};
/*
js intl, inspired by danabr/jsI18n
*/
class JsI18n {
constructor() {
this.locale = ""; //Current locale
this.locales = new Array(); //Available locales
this.overwrites = new Array();
this.defaultLocale = "fr";
}
/*
Method for automatically detecting the language, does not work in every browser.
*/
async detectLanguage() {
const customLangs = document.querySelectorAll("orbit-lang");
if (customLangs) {
for (const lang of customLangs) {
const name = lang.getAttribute("lang");
const file = lang.getAttribute("file");
const result = await fetch(file);
if (result.ok) {
const json = await result.json();
if (this.overwrites[name.toString()] !== undefined) {
this.mergeDeep(this.overwrites[name.toString()], json);
} else {
this.overwrites[name.toString()] = json;
}
}
}
}
this.resumeDetection();
}
resumeDetection() {
const langComponent = document.querySelector("orbit-fallback-lang");
if (langComponent) {
if (langComponent.hasAttribute("lang")) {
this.defaultLocale = langComponent.getAttribute("lang");
if (langComponent.hasAttribute("force")) {
localStorage.setItem("language", this.defaultLocale);
}
}
}
if (
localStorage.getItem("language") ||
(window.navigator.language !== null &&
window.navigator.language !== undefined)
) {
this.fetchLocale(this.defaultLocale);
this.setLocale(
localStorage.getItem("language") ||
window.navigator.language.slice(0, 2),
);
} else {
console.error("Language not found");
this.setLocale(this.defaultLocale);
}
}
isObject(item) {
return item && typeof item === "object" && !Array.isArray(item);
}
mergeDeep(target, ...sources) {
if (!sources.length) return target;
const source = sources.shift();
if (this.isObject(target) && this.isObject(source)) {
for (const key in source) {
if (this.isObject(source[key])) {
if (!target[key])
Object.assign(target, {
[key]: {},
});
this.mergeDeep(target[key], source[key]);
} else {
Object.assign(target, {
[key]: source[key],
});
}
}
}
return this.mergeDeep(target, ...sources);
}
/*
Translates tag contents and
attributes depending on the
value of key.
*/
translateTag(node, key) {
if (key.indexOf("=") === -1) {
//Simple key
this.translateNodeContent(node, key);
} else {
//Attribute/key pairs
const parts = key.split(";");
for (let i = 0; i < parts.length; i++) {
const pair = parts[i].split("=");
const attr = pair[0].toLowerCase().trim();
const k = pair[1].trim();
if (attr === "html") {
this.translateNodeContent(node, k);
} else {
// https://git.startinblox.com/framework/sib-core/issues/733
if (attr.startsWith("label-")) {
// biome-ignore lint/style/useConst: <explanation>
let label = node.querySelector(
`[name="${attr.replace("label-", "")}"] > label`,
);
if (label != null) {
this.translateNodeContent(label, k);
}
}
// https://git.startinblox.com/framework/sib-core/issues/755
if (attr.startsWith("placeholder-")) {
const placeholder = node.querySelector(
`[placeholder="${attr.replace("placeholder-", "")}"]`,
);
if (placeholder != null) {
this.translateNodeContent(
placeholder.attributes["placeholder"],
k,
);
const input = node.querySelector(
`[name="${attr.replace("placeholder-", "")}"] > input`,
);
if (input != null) {
this.translateNodeContent(input.attributes["placeholder"], k);
}
}
}
if (attr.startsWith("enum-")) {
const enumAttr = node.querySelector(
`[name="${attr.replace("enum-", "")}"]`,
);
if (enumAttr != null) {
this.translateNodeContent(enumAttr.attributes["enum"], k);
}
}
this.translateNodeContent(node.attributes[attr], k);
}
}
}
}
/**
Replace the content of the given node
if there is a translation for the given key.
**/
translateNodeContent(node, key) {
const translation = this.t(key);
if (node != null && translation !== undefined) {
if (node.nodeType === 1) {
//Element
try {
if (node.innerHTML !== translation) node.innerHTML = translation;
} catch (e) {
if (node.text !== translation) node.text = translation;
}
} else if (node.nodeType === 2) {
//Attribute
if (node.value !== translation) node.value = translation;
}
}
}
/*
Helper for translating a node
and all its child nodes.
*/
processNode(node) {
if (node !== undefined) {
if (node.nodeType === 1) {
//Element node
const key = node.attributes["data-trans"];
if (key != null) {
this.translateTag(node, key.nodeValue);
}
}
//Process child nodes
const children = node.childNodes;
for (let i = 0; i < children.length; i++) {
this.processNode(children[i]);
}
}
}
/*
Adds a locale to the list,
replacing the translations
if the locale is already defined.
*/
addLocale(locale, translations) {
if (this.overwrites[locale.toString()] !== undefined) {
this.mergeDeep(translations, this.overwrites[locale.toString()]);
}
this.locales[locale.toString()] = translations;
}
fetchLocale(locale) {
return fetch(`/locales/${locale}.json`).then((result) => {
if (result.ok) {
result.json().then((e) => {
this.addLocale(locale, e);
});
}
});
}
/*
Sets the locale to use when translating.
*/
setLocale(locale) {
try {
this.fetchLocale(locale).then(() => {
if (this.locale) {
localStorage.setItem("language", this.locale);
}
this.processPage();
this.locale = locale;
});
} catch {
if (locale !== this.defaultLocale) {
console.warn(
`Locale not found: ${locale}, fallback to ${this.defaultLocale}`,
);
this.setLocale(this.defaultLocale);
} else {
console.error("Language not found");
}
}
}
/*
Fetches the translation associated with the given key.
*/
t(key) {
let translations = this.locales[this.locale];
if (translations === undefined) {
if (this.locales.length > 1) {
translations = this.locales[this.defaultLocale];
}
}
if (translations !== undefined) {
let translation = key
.toString()
.split(".")
.reduce((o, i) => (o ? o[i] : undefined), translations);
if (typeof translation === "string") {
return translation;
}
try {
const keySplitted = key.toString().split(".");
const first = keySplitted.shift();
translation = translations[first][keySplitted.join(".")];
return translation;
} catch {
return translations[key.toString()];
}
}
return undefined;
}
/*
Alias for JsI18n.t
*/
translate(key) {
this.t(key);
}
/**
Replaces the contents of all tags
that have the data-trans attribute set.
**/
processPage() {
this.processNode(document.getElementsByTagName("html")[0]);
}
}
//Global
window.orbit.intl = new JsI18n();
document.addEventListener("DOMContentLoaded", () => {
// Detect the lang & initialize, based on the browser or "language" item from localstorage
window.orbit.intl.detectLanguage();
let timer;
let timer2;
new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.target.attributes["data-trans"] != null) {
// Render the target of the mutation instantly
window.orbit.intl.processNode(mutation.target);
// Then wait one arbitrary second to re-render the whole document in case a widget re-rendered
clearTimeout(timer);
timer = setTimeout(
() => window.orbit.intl.processNode(document.querySelector("body")),
500,
);
}
// Hotfix for Secoia
if (mutation.target.nodeName === "SOLID-DIRECTORY") {
if (!timer2) {
setTimeout(() => mutation.target.render(), 1000);
timer2 = setTimeout(() => mutation.target.render(), 2000);
}
}
if (mutation.target.nodeName === "SOLID-COMMUNITIES") {
if (!timer2) {
setTimeout(() => mutation.target.render(), 1000);
timer2 = setTimeout(() => mutation.target.render(), 2000);
}
}
}
}).observe(document.body, {
subtree: true,
childList: true,
});
document.addEventListener("widgetRendered", (event) => {
window.orbit.intl.processNode(event.target);
// Then wait one arbitrary second to re-render the whole document in case a widget re-rendered
clearTimeout(timer);
timer = setTimeout(
() => window.orbit.intl.processNode(document.querySelector("body")),
500,
);
});
});