') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); GitHub - HealthTree/firestore-join · GitHub
Skip to content

Latest commit

History

76 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Firestore Join

Easily include references inside your document.

Supports:

  • Single references
  • Arrays of references
  • Maps pointing references
  • Infinite nested references
  • Cache so you don't include .get() the same reference more than once.

npm i firebase-join

How to use:

1 - Install:

npm i firebase-join

2 - Import:

import { SerializedDocumentArray } from '@healthtree/firestore-join';

The building blocks for this library are SerializedDocument and SerializedDocumentArray classes & IncludeConfig.

//Only recommended methods and properties are documented in this interfaceinterfaceSerializedDocument{// Data returned after calling snapshot.data() and transforming the data, by default it converts firestore timestamps into JS dates.
data: any// Firestore document reference
ref: firebase.firestore.DocumentReference// Any included documents, as SerializedDocuments.
included: Object={}// Promises for each included reference, this promises resolves once the document is returned by the server or cache.
promises: Object={}// DocumentSnapshot of the document if document came from server.
snapshot: DocumentSnapshotconstructor(snapshot: DocumentSnapshot,includeConfig: IncludeConfig={}){
...
}// Create and return a SerializedDocument that doesn't exist on firestore, useful to keep consistency.staticcreateLocal=(ref: DocumentReference,data: any={},includeConfig: IncludeConfig={}): SerializedDocument=>{
...
}// Gets the document reference and returns a SerializedDocument with any includeConfigstaticfromDocumentReference=(ref: DocumentReference,includeConfig: IncludeConfig): SerializedDocumentPromise=>{
... }}
// SerializedDocumentArray is basically an array of SerializedDocuments// It implements a ready function to know when all included documents are readyinterfaceSerializedDocumentArrayextendsArray<SerializedDocument>{constructor(querySnapshot: QuerySnapshot,includesConfig: IncludeConfig){}staticfromDocumentReferenceArray=(documentReferenceArray: [DocumentReference],includesConfig: IncludeConfig): SerializedDocumentArrayPromise=>{
...
})}// Returns a promise that resolves a SerializedDocumentArray// when the documents (without includes) are ready.
static fromQuery=(query: Query,includesConfig: IncludeConfig): SerializedDocumentArrayPromise=>{
...
}// Returns a promise that resolves when all included documents are readyready(){}}

To serialize an array of documents without including any references.

constposts=awaitSerializedDocumentArray.fromQuery(firestore.collection('posts'));// or with any firestore supported filtersconstpostsFromUser=awaitSerializedDocumentArray.fromQuery(firestore.collection('posts').where('user','==',userReference));

To serialize an array of documents including a reference and waiting for all the included references to be ready.

Let's pretend each post has a property called user, where user is a documentReference of the user that created the post.

To include all the users, you pass an includeConfig object as the second parameter and call a ready function that returns a promise that resolves once all the references are resolved.

constposts=awaitSerializedDocumentArray.fromQuery(firestore.collection('posts'),{user: true}).ready();// with any firestore supported filtersconstpostsFromUser=awaitSerializedDocumentArray.fromQuery(firestore.collection('posts').where('user','==',userReference),{user: true}).ready();

To serialize an array of documents including a reference and waiting for all the included references to be ready.

Let's pretend each post has a property called user, where user is a documentReference of the user that created the post.

To include all the users, you pass an includeConfig object as the second parameter and call a ready function that returns a promise that resolves once all the references are resolved.

constposts=awaitSerializedDocumentArray.fromQuery(firestore.collection('posts'),{user: true}).ready();// with any firestore supported filtersconstpostsFromUser=awaitSerializedDocumentArray.fromQuery(firestore.collection('posts').where('user','==',userReference),{user: true}).ready();// with nested references to includeconstposts=awaitSerializedDocumentArray.fromQuery(firestore.collection('posts'),{user: {organization: true}}).ready();// You can access the included documentsconsole.log(posts[0].includes.user);// User dataconsole.log(posts[0].includes.user.includes.organization.data);// User->Organization data// if included documents are an arrayconstposts=awaitSerializedDocumentArray.fromQuery(firestore.collection('posts'),{user: true,tags: true}).ready();// You can access the included documentsconsole.log(posts[0].includes.tags);// Array of SerializedDocuments containing all tags

Listen to firestore query snapshots and serialize

// Only use if your use case really justifies real time updates.letposts;firestore.collection('posts').onSnapshot(asyncquerySnapshot=>{posts=awaitnewSerializedDocumentArray(querySnapshot,{user: true}).ready()})

When to use createLocal?

Let's pretend we have a page/component used create or edit a document in firestore.

// Sample using svelteonMount(async()=>{constpostId=$page.query.postId;letpost;if(postId==='new'){// Pass the desired reference and any initial datapost=SerializedDocument.createLocal(db.collection('posts').doc(),{user: userReference})}else{post=awaitSerializedDocument.fromDocumentReference(db.collection('posts').doc(postId))}})// UI to modify the message on post, no need for double ui if post is newonSave=()=>{post.ref.set(post.data);// No need to have extra logic to see if it was a new doc}

Advanced - Include documents but listen to individual include to be ready.

constposts=awaitSerializedDocumentArray.fromQuery(firestore.collection('posts'),{user: true});// We are not going to wait for includes to be ready, start rendering and only render user name when ready.// Sample using svelte{#each postsaspost(post.ref.id)}<div>{#await post.promises.user}<p>...waiting</p>{:thenuser}<p>From: {post.included.user.data.name}</p>{:catcherror}{/await}{post.data.message}</div>{/each}//This allows for fast, progressive ui rendering // where you don't have to wait for everything to be ready

About

No description, website, or topics provided.

Resources

Contributing

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages