This Add-On allows you to translate documents using the Google Translate service. Supply a two character ISO 639-1 code for the input and output languages and you will receive your translation in a download as well as it will be uploaded to DocumentCloud. See https://cloud.google.com/translate/docs/languages for supported languages.
This Add-On uses Azure’s Document Intelligence API to OCR documents. The document(s) must be public to be processed. This Add-On uses 1 AI Credit per page.
A modified implementation of the example using GPT-3 to classify documents, described by Nick Diakopoulos. For simplicity’s sake, it only implements the first half of the classification task described, but allows the user to customize the subject matter. Use with care as it may introduce factual errors, hallucinations, and other artifacts in summaries.
\n
Note that this plugin uses 14 credits per document analyzed, rounded up. Give this a full prompt that you would pass into GPT-3 and it will run it against your selected documents, one at a time. The Add-On only looks at the 12,000 characters of a document. The prompt refers to the user input as the “Assignment,” the document’s text as “Document Text”, and GPT-3’s generated response as the “Answer,” which can be helpful when defining your prompt.
\n
We’d love your feedback and ideas — drop a note to info at documentcloud dot org or join us in the News Nerdery Slack in the #proj-documentcloud channel.
This Add-On uses Azure’s Document Intelligence API to OCR documents. The document(s) must be public to be processed. This Add-On uses 1 AI Credit per page.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed dapibus id
+ metus vel euismod. Phasellus luctus, orci sit amet vestibulum tincidunt,
+ purus erat interdum ex, vel bibendum tortor justo sit amet lectus. Fusce
+ consequat, ligula non viverra auctor, velit nulla elementum lectus, nec
+ luctus felis libero a quam. In hac habitasse platea dictumst. Vivamus
+ euismod, purus non tincidunt blandit, erat augue dapibus metus, et aliquam
+ sapien elit ac orci. Integer ut justo nec justo vestibulum consectetur ut
+ non nisi. Sed vestibulum eget tortor eget tristique. Nulla facilisi. Cras
+ eget vehicula quam, id scelerisque ipsum. Nunc sagittis elit vitae justo
+ viverra, at condimentum lectus tristique. Integer nec malesuada purus.
+ Nullam eu bibendum libero.
+
+
+
diff --git a/src/common/stories/Dropdown.stories.svelte b/src/common/stories/Dropdown.stories.svelte
new file mode 100644
index 000000000..874384486
--- /dev/null
+++ b/src/common/stories/Dropdown.stories.svelte
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/langs/json/en.json b/src/langs/json/en.json
index 8210a6fb5..aae1b6a34 100644
--- a/src/langs/json/en.json
+++ b/src/langs/json/en.json
@@ -350,20 +350,51 @@
"addOnsMenu": "Add-Ons"
},
"authSection": {
- "help": "Help",
- "language": "Language",
- "faq": "FAQ",
- "searchDocs": "Search Documentation",
- "apiDocs": "API Documentation",
- "addOns": "Add-Ons",
- "premium": "DocumentCloud Premium",
- "emailUs": "Email Us",
- "acctSettings": "Account settings",
- "signOut": "Sign out",
- "changeOrg": "Change organization",
- "personalAcct": "Personal Account",
- "signIn": "Sign in",
- "uploadEmail": "Upload via email"
+ "help": {
+ "title": "Help",
+ "faq": "FAQ",
+ "searchDocs": "Search Documentation",
+ "apiDocs": "API Documentation",
+ "addOns": "Add-Ons",
+ "premium": "DocumentCloud Premium",
+ "emailUs": "Email Us"
+ },
+ "language": {
+ "title": "Language"
+ },
+ "user": {
+ "uploadEmail": "Upload via email",
+ "acctSettings": "Account settings",
+ "signOut": "Sign out",
+ "signIn": "Sign in"
+ },
+ "premiumUpgrade": {
+ "title": "Premium",
+ "heading": "Go Pro with DocumentCloud’s premium tools",
+ "orgHeading": "Power your team with DocumentCloud’s premium tools",
+ "description": "Upgrade to a Professional plan to search document annotations and access Premium Add-Ons like advanced OCR, GPT-driven document analysis, and more.",
+ "orgDescription": "Upgrade to an Organization plan to search document annotations and access Premium Add-Ons like advanced OCR, GPT-driven document analysis, and more.",
+ "docs": "Learn more about DocumentCloud Premium",
+ "addons": "Explore premium add-ons",
+ "orgs": "Join or start an organization",
+ "cta": "Upgrade Plan"
+ },
+ "org": {
+ "changeOrg": "Change organization",
+ "personalAcct": "Personal Account",
+ "adminRole": "Admin",
+ "memberListError": "An error occurred while loading the member list.",
+ "memberListEmpty": "This organization has no other members."
+ },
+ "credits": {
+ "monthlyOrg": "Monthly Org Allowance",
+ "monthlyPro": "Monthly Pro Allowance",
+ "refreshOn": "Credit allowance will reset on {date}",
+ "purchased": "Purchased Credits",
+ "purchasedHelpText": "Purchased credits never expire and will only be used after you run out of monthly credits.",
+ "purchaseCreditsButton": "Purchase Credits",
+ "purchaseCreditsAdminOnly": "Only org admins may purchase additional credits."
+ }
},
"documents": {
"yourDocuments": "Your Documents",
@@ -706,6 +737,8 @@
"addons": "Add-Ons",
"backButton": "Browse Add-Ons",
"select": "Documents to run this Add-On against:",
+ "premium": "This Add-On uses Premium Credits:",
+ "premiumSpendLimit": "Set a spending limit",
"queryNoSelected": "This Add-On will try to run against the {n, plural, one {# document} other {# documents}} currently included in your search results. To run it against only selected documents, cancel this and select some, then select the Add-On again.",
"noSelected": "You must select some documents to run against. Cancel this and select some, then select the Add-On again.",
"runSelected": "This Add-On will try to run against the {n, plural, one {# currently selected document} other {# currently selected documents}}.",
@@ -729,7 +762,13 @@
"scheduleSuccess": "Add-on is now scheduled",
"runSuccess": "Add-on is now running",
"selectionHelp": "From the main list, select individual documents or run a search for the documents you want, for example “+project:mueller-docs-200005”.",
- "selectionLearnMore": "Learn more about how Add-Ons work"
+ "selectionLearnMore": "Learn more about how Add-Ons work",
+ "cost": "{amount} {unit} per {price, plural, one {credit} other {# credits}}",
+ "premiumUpgrade": {
+ "message": "This Premium Add-On uses AI to perform advanced analysis. Upgrade to a {plan} account to utilize this and other powerful Add-Ons.",
+ "callToAction": "Upgrade Plan",
+ "memberMessage": "This Premium Add-On uses AI to perform advanced analysis. Contact your organization admin about upgrading your plan."
+ }
},
"addonBrowserDialog": {
"title": "Browse Add-Ons",
@@ -747,7 +786,8 @@
"viewsource": "View Source",
"usage": "Usage",
"pinnedTip": "Quickly access your favorite Add-Ons by clicking the “Pin” icon next to its name. They’ll then be available to run from the Add-Ons dropdown menu.",
- "featuredTip": "Here’s some of the DocumentCloud team’s favorite Add-Ons, including both new additions as well as classics we think every user should try."
+ "featuredTip": "Here’s some of the DocumentCloud team’s favorite Add-Ons, including both new additions as well as classics we think every user should try.",
+ "premiumTip": "Premium add-ons have powerful functionality backed by AI and cloud services. They have an additional cost to run and are only available to Professional and Organizational users."
},
"addonRuns": {
"scheduled": "Scheduled Add-Ons",
@@ -783,11 +823,12 @@
"disable": "Disable upload via email"
},
"anonymous": {
- "title": "Welcome to DocumentCloud, an open document archive from MuckRock!",
- "p1": "This site helps organize, analyze and host millions of records contributed by verified newsrooms, research organizations and other groups that help inform the public through the user of primary source materials. Using the search bar above, you can browse through {n} publicly published documents, with thousands more added ever day.",
- "p2": "If you're part of a newsroom, academic organization, or other public-interest organization that vets and publishes materials in the public interest, you can register here and request verification to upload materials, or learn more about DocumentCloud and it's powerful suite of hosting, analysis and publication tools.",
- "p3": "Want more fascinating documents, open data and original reporting to your inbox? Subscribe to MuckRock's newsletter:",
- "p4": "DocumentCloud is part of a suite of transparency tools from the MuckRock Foundation, a 501c3 registered non-profit. This archive is open to the public and advertisement free thanks to support from readers like you — you can learn more about our work or make a donation.",
- "subscribe": "Subscribe"
- }
+ "title": "Welcome to DocumentCloud, an open document archive from MuckRock!",
+ "p1": "This site helps organize, analyze and host millions of records contributed by verified newsrooms, research organizations and other groups that help inform the public through the user of primary source materials. Using the search bar above, you can browse through {n} publicly published documents, with thousands more added ever day.",
+ "p2": "If you're part of a newsroom, academic organization, or other public-interest organization that vets and publishes materials in the public interest, you can register here and request verification to upload materials, or learn more about DocumentCloud and it's powerful suite of hosting, analysis and publication tools.",
+ "p3": "Want more fascinating documents, open data and original reporting to your inbox? Subscribe to MuckRock's newsletter:",
+ "p4": "DocumentCloud is part of a suite of transparency tools from the MuckRock Foundation, a 501c3 registered non-profit. This archive is open to the public and advertisement free thanks to support from readers like you — you can learn more about our work or make a donation.",
+ "subscribe": "Subscribe"
+ },
+ "premium": {}
}
diff --git a/src/langs/langs.json b/src/langs/langs.json
index 782bd914d..4395197fc 100644
--- a/src/langs/langs.json
+++ b/src/langs/langs.json
@@ -1,8 +1,8 @@
[
- ["English", "en"],
- ["Español", "es"],
- ["Français", "fr"],
- ["українська", "uk"],
- ["русский", "ru"],
- ["Deutsche", "de"]
+ ["US English", "en", "🇺🇸"],
+ ["Español", "es", "🇪🇸"],
+ ["Français", "fr", "🇫🇷"],
+ ["Deutsche", "de", "🇩🇪"],
+ ["українська", "uk", "🇺🇦"],
+ ["русский", "ru", "🇷🇺"]
]
diff --git a/src/manager/orgsAndUsers.js b/src/manager/orgsAndUsers.js
index 3e965f629..257d0eced 100644
--- a/src/manager/orgsAndUsers.js
+++ b/src/manager/orgsAndUsers.js
@@ -11,6 +11,7 @@ import {
getUsers,
getOrganization,
} from "../api/orgAndUser.js";
+import { SQUARELET_URL } from "../api/auth.js";
import { projects, initProjects } from "./projects.js";
import { userUrl, allDocumentsUrl } from "../search/search.js";
import { layout } from "./layout.js";
@@ -116,7 +117,11 @@ function initProjectsIfNecessary(route) {
}
export async function initOrgsAndUsers(callback = null) {
- orgsAndUsers.me = await getMe();
+ try {
+ orgsAndUsers.me = await getMe();
+ } catch (e) {
+ orgsAndUsers.me = null;
+ }
if (orgsAndUsers.me !== null) {
// Logged in
orgsAndUsers.usersById[orgsAndUsers.me.id] = orgsAndUsers.me;
@@ -127,11 +132,15 @@ export async function initOrgsAndUsers(callback = null) {
const org = orgsAndUsers.selfOrgs[i];
orgsAndUsers.orgsById[org.id] = org;
}
-
- orgsAndUsers.sameOrgUsers = await inMyOrg(
- orgsAndUsers.me.organization,
- orgsAndUsers.me,
- );
+ try {
+ orgsAndUsers.sameOrgUsers = await inMyOrg(
+ orgsAndUsers.me.organization.id,
+ orgsAndUsers.me.id,
+ );
+ } catch (err) {
+ console.error(err);
+ orgsAndUsers.sameOrgUsers = [];
+ }
// Trigger update
orgsAndUsers.usersById = orgsAndUsers.usersById;
@@ -173,10 +182,15 @@ export async function changeActive(org) {
orgsAndUsers.me.organization = org;
orgsAndUsers.me = orgsAndUsers.me;
- orgsAndUsers.sameOrgUsers = await inMyOrg(
- orgsAndUsers.me.organization,
- orgsAndUsers.me,
- );
+ try {
+ orgsAndUsers.sameOrgUsers = await inMyOrg(
+ orgsAndUsers.me.organization.id,
+ orgsAndUsers.me.id,
+ );
+ } catch (err) {
+ console.error(err);
+ orgsAndUsers.sameOrgUsers = [];
+ }
pushToast("Successfully changed active organization");
});
}
@@ -185,26 +199,59 @@ export async function usersInOrg(orgId) {
return getUsers({ orgIds: [orgId] });
}
+function alphabetizeUsers(userA, userB) {
+ const aName = String(userA.name || userA.username);
+ const bName = String(userB.name || userB.username);
+ return aName.localeCompare(bName);
+}
+
// same as above, but exclude me
-export async function inMyOrg(organization, me) {
- if (!organization.id) return [];
- const users = await getUsers({ orgIds: [organization.id] }).catch((e) => {
- console.error(e);
- return [];
- });
+export async function inMyOrg(orgId, myId) {
+ if (!orgId) return [];
+ const users = await getUsers({ orgIds: [orgId] });
+ // Sort by admin status, then username
+ const adminUsers = users
+ .filter((u) => u.admin_organizations.includes(orgId))
+ .sort(alphabetizeUsers);
+ const regularUsers = users
+ .filter((u) => !adminUsers.includes(u))
+ .sort(alphabetizeUsers);
+ // Remove me from the user list
+ return [...adminUsers, ...regularUsers].filter((u) => u.id !== myId);
+}
- users.sort((a, b) => {
- // Sort by admin status, then username
- const aAdmin = a.admin_organizations.includes(organization.id);
- const bAdmin = b.admin_organizations.includes(organization.id);
- if (aAdmin == bAdmin) {
- return String(a.name || a.username).localeCompare(
- String(b.name || b.username),
- );
- } else {
- return aAdmin < bAdmin;
- }
- });
+export function isOrgAdmin(user) {
+ if (!user) return false;
+ const id =
+ typeof user.organization === "string"
+ ? user.organization
+ : user.organization.id;
+ return user.admin_organizations.includes(id);
+}
+
+export function isPremiumOrg(org) {
+ if (!org || !org.plan) return null;
+ return org.plan !== "Free";
+}
+
+export function getCreditBalance(org) {
+ if (!org) return null;
+ return org.monthly_credits + org.purchased_credits;
+}
+
+export async function triggerPremiumUpgradeFlow(org) {
+ let url;
+ if (org.individual) {
+ // Redirect the user to their Squarelet account settings
+ url = SQUARELET_URL + `/users/~payment/`;
+ } else {
+ // Redirect the user to the Squarelet organization settings
+ url = SQUARELET_URL + `/organizations/${org.slug}/payment/`;
+ }
+ window?.open(url);
+}
- return users.filter((u) => u.id !== me.id);
+// TODO: Handle flow for purchasing premium credits (#342)
+export async function triggerCreditPurchaseFlow() {
+ alert("Purchase Credits!");
}
diff --git a/src/pages/app/AccountNavigation/AccountNavigation.svelte b/src/pages/app/AccountNavigation/AccountNavigation.svelte
new file mode 100644
index 000000000..5e7f4c9c8
--- /dev/null
+++ b/src/pages/app/AccountNavigation/AccountNavigation.svelte
@@ -0,0 +1,60 @@
+
+
+
+
+
diff --git a/src/pages/app/AccountNavigation/HelpMenu.svelte b/src/pages/app/AccountNavigation/HelpMenu.svelte
new file mode 100644
index 000000000..2270d0cbf
--- /dev/null
+++ b/src/pages/app/AccountNavigation/HelpMenu.svelte
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+