Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[sync] Add syncing with link to relation #633

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

auscyber
Copy link
Contributor

@auscyber auscyber commented Jan 8, 2025

Here you can make collections be associated with a specific relation on another database, this is useful for me when i have a subject database or a topic database, and i want to link sources with that field on another database, its not perfect code right now, but it does work

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for linked databases in Notion integration
    • Introduced new preferences for collection sync configuration
    • Enhanced dialog interface for collection settings
  • Improvements

    • Updated build configuration
    • Expanded sync job parameters
    • Improved property builder functionality
  • Development

    • Added Nix shell configuration for development environment
    • Updated type definitions and localization support
  • Technical Updates

    • Modified sync configuration handling
    • Added new utility functions for menu item management

Copy link

coderabbitai bot commented Jan 8, 2025

Walkthrough

This pull request introduces comprehensive changes to the Notero extension, focusing on enhancing collection synchronization and preferences. The modifications span multiple files, adding support for linked databases, updating build configurations, and introducing new dialog interfaces. The changes primarily revolve around improving the user's ability to manage collection settings, particularly in relation to Notion database integration, with updates to type definitions, UI components, and synchronization logic.

Changes

File Change Summary
scripts/build.ts Added new build configuration for src/content/prefs/dialog.tsx
shell.nix Introduced Nix shell configuration with Node.js and pnpm
src/bootstrap.ts Added chromeHandle variable and Chrome registration logic
src/content/prefs/collection-sync-config.ts Added optional associatedLink to CollectionSyncConfig type
src/content/prefs/dialog.tsx New Dialog class for managing preference dialogs
src/content/prefs/dialog.xhtml New XML file defining dialog user interface
src/content/prefs/notero-pref.ts Added linkedCollectionID to preferences
src/content/prefs/preferences.tsx Updated to support linked database menu
src/content/prefs/preferences.xhtml Added new menulist for linked database
src/content/prefs/sync-configs-table.tsx Added support for associated links in sync configurations
src/content/sync/property-builder.ts Introduced collection relation mapping and new property definitions
src/content/sync/sync-job.ts Updated to handle collection relation mapping
src/locale/en-US/notero.ftl Added localization for linked database preference

Sequence Diagram

sequenceDiagram
    participant User
    participant Preferences
    participant Dialog
    participant NotionClient
    participant SyncConfigs

    User->>Preferences: Open Preferences
    Preferences->>Dialog: Open Collection Dialog
    Dialog->>NotionClient: Fetch Databases
    NotionClient-->>Dialog: Return Database List
    Dialog->>User: Display Database Selection
    User->>Dialog: Select Linked Database
    Dialog->>SyncConfigs: Update Configuration
    SyncConfigs-->>Dialog: Confirm Update
    Dialog->>Preferences: Return Updated Settings
Loading

Possibly related PRs

Poem

🐰 A Rabbit's Build Adventure 🚀

In scripts and configs, changes unfurl,
Linked databases start to twirl,
Preferences dance with newfound grace,
A synchronization embrace!
Code hops forward, feature-bright! 🌟

Finishing Touches

  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

socket-security bot commented Jan 8, 2025

New and updated dependencies detected. Learn more about Socket for GitHub ↗︎

Package New capabilities Transitives Size Publisher
npm/@types/[email protected] 🔁 npm/@types/[email protected] None 0 37.9 kB types
npm/@types/[email protected] 🔁 npm/@types/[email protected] None +2 1.69 MB types
npm/[email protected] 🔁 npm/[email protected] Transitive: environment, filesystem +39 7.9 MB jameshenry
npm/[email protected] 🔁 npm/[email protected] None 0 0 B

View full report↗︎

@auscyber auscyber marked this pull request as ready for review January 12, 2025 03:28
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (11)
src/content/sync/property-builder.ts (2)

235-247: Improve type safety and readability of Link property.

The current implementation could be more robust and type-safe.

Consider this improved implementation:

    {
      name: 'Link',
      type: 'relation',
      buildRequest: () =>
        Zotero.Collections.get(this.item.getCollections())
          .map(
            (collection) =>
-              this.collectionRelationMap[collection.id] && {
-                id: this.collectionRelationMap[collection.id],
-              },
+              {
+                id: this.collectionRelationMap[collection.id],
+              },
          )
-          .filter((x) => x != undefined) as { id: string }[];
+          .filter((x): x is { id: string } => 
+            typeof x.id === 'string' && x.id.length > 0
+          );

382-388: Extract URI manipulation to a utility function.

The direct link generation logic could be more maintainable if moved to a utility function.

Consider extracting the logic:

+ // In utils.ts
+ export function getZoteroDirectLink(item: Zotero.Item): string {
+   const itemURI = Zotero.URI.getItemURI(item);
+   const itemKey = itemURI.split('/').pop();
+   return `zotero://select/library/items/${itemKey}`;
+ }

  // In property-builder.ts
  {
    name: 'Zotero Direct Link',
    type: 'url',
-    buildRequest: () => {
-      return `zotero://select/library/items/${Zotero.URI.getItemURI(this.item).split('/').pop()}`;
-    },
+    buildRequest: () => getZoteroDirectLink(this.item),
  },
shell.nix (1)

1-10: Consider pinning nixpkgs version for better reproducibility

The shell configuration looks good and includes the necessary dependencies. However, consider pinning the nixpkgs version to ensure consistent development environments across the team.

Example of pinning nixpkgs:

-{ pkgs ? import <nixpkgs> {} }:
+{ pkgs ? import (fetchTarball {
+    url = "https://github.com/NixOS/nixpkgs/archive/nixos-23.11.tar.gz";
+    sha256 = "0000000000000000000000000000000000000000000000000000"; # Replace with actual hash
+  }) {} }:
src/content/prefs/dialog.xhtml (1)

32-45: Improve grid layout and accessibility

The grid layout needs improvement:

  1. Missing column definitions
  2. Missing aria-labels for accessibility
       <label>Collection Settings</label>
       <grid>
+        <columns>
+          <column flex="1"/>
+          <column flex="2"/>
+        </columns>
         <rows>
           <row>
-            <label value="Associated Link:" />
+            <label value="Associated Link:" control="notero-associatedLink"/>
             <menulist id="notero-associatedLink">
               <menupopup />
             </menulist>
           </row>
           <row>
-            <label value="Sync Enabled:" />
+            <label value="Sync Enabled:" control="notero-syncEnabled"/>
             <checkbox id="notero-syncEnabled" />
           </row>
src/content/utils/elements.ts (1)

36-41: Consider making MenuItem properties more type-safe

The MenuItem type could be improved for better type safety:

 export type MenuItem = {
   disabled?: boolean;
-  l10nId?: FluentMessageId;
-  label?: string;
+  text: {
+    l10nId: FluentMessageId;
+  } | {
+    label: string;
+  };
   value: string;
 };
src/content/prefs/dialog.tsx (2)

14-19: Consider adding a constructor for field initialization.

The class could benefit from having a constructor to initialize the private fields, making the initialization flow more explicit and ensuring fields are properly set up before init() is called.

 class Dialog {
   private collectionDialogContainer!: XUL.XULElement;
   private associatedLinkElement!: XUL.MenuListElement;
   private syncEnabledElement!: XUL.CheckboxElement;
   private params!: DialogArguments;
+
+  constructor() {
+    this.collectionDialogContainer = null!;
+    this.associatedLinkElement = null!;
+    this.syncEnabledElement = null!;
+    this.params = null!;
+  }

70-72: Consider using a factory pattern for better testability.

Direct instantiation in module exports can make unit testing difficult. Consider using a factory pattern or dependency injection.

+class DialogFactory {
+  private static instance: Dialog | null = null;
+
+  static getInstance(): Dialog {
+    if (!this.instance) {
+      this.instance = new Dialog();
+    }
+    return this.instance;
+  }
+}
+
 module.exports = {
-  dialog: new Dialog(),
+  dialog: DialogFactory.getInstance(),
+  // Export factory for testing
+  DialogFactory,
 };
src/bootstrap.ts (1)

11-12: Consider using a more explicit variable name.

The variable name chromeHandle could be more descriptive of its purpose.

-let chromeHandle;
+let noteroContentChromeHandle;
types/components/virtualized-table.d.ts (1)

2-2: Consider using a more specific type for WindowedList.

Using unknown type loses type safety. If possible, define an interface with the expected methods and properties that WindowedList should have.

-  type WindowedList = unknown;
+  interface WindowedList {
+    // Add expected methods/properties here
+    invalidate(): void;
+    invalidateRow(index: number): void;
+    // Add other required members
+  }
src/content/prefs/sync-configs-table.tsx (1)

190-212: Consider extracting dialog handling to a separate method.

The handleActivate method is doing too much. Consider extracting the dialog handling and config update logic to improve readability and maintainability.

+  private updateConfigFromDialogResult(
+    collection: Zotero.Collection,
+    result: { syncEnabled: boolean; associatedLink: string },
+  ) {
+    this.syncConfigs = {
+      ...this.syncConfigs,
+      [collection.id]: {
+        syncEnabled: result.syncEnabled,
+        associatedLink: result.associatedLink,
+      },
+    };
+    this.invalidateRows();
+    this.table?.invalidate();
+  }
+
   handleActivate = (_event: KeyboardEvent | MouseEvent, indices: number[]) => {
     if (indices.length == 1 && indices[0] !== undefined) {
       const index = indices[0];
       const row = this.rows[index];
       if (!row) return;
 
       const result = this.openCollectionDialog(
         row.collection,
         row.syncEnabled,
         row.associatedLink,
       );
 
       if (result) {
-        this.syncConfigs = {
-          ...this.syncConfigs,
-          [row.collection.id]: {
-            syncEnabled: result.syncEnabled,
-            associatedLink: result.associatedLink,
-          },
-        };
-        this.invalidateRows();
-        this.table?.invalidate();
+        this.updateConfigFromDialogResult(row.collection, result);
       }
     }
   };
src/content/prefs/collection-sync-config.ts (1)

8-8: LGTM! Consider enhancing type validation.

The addition of the optional associatedLink property aligns well with the PR objectives. However, the isCollectionSyncConfig type guard at line 103 only validates the presence of syncEnabled. Consider enhancing it to validate the associatedLink type when present.

 function isCollectionSyncConfig(value: unknown): value is CollectionSyncConfig {
-  return isObject(value) && 'syncEnabled' in value;
+  return isObject(value) && 
+    'syncEnabled' in value && 
+    (!('associatedLink' in value) || typeof value.associatedLink === 'string');
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59e1b30 and 5d0d3ef.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (17)
  • scripts/build.ts (1 hunks)
  • shell.nix (1 hunks)
  • src/bootstrap.ts (2 hunks)
  • src/content/prefs/collection-sync-config.ts (1 hunks)
  • src/content/prefs/dialog.tsx (1 hunks)
  • src/content/prefs/dialog.xhtml (1 hunks)
  • src/content/prefs/notero-pref.ts (3 hunks)
  • src/content/prefs/preferences.tsx (6 hunks)
  • src/content/prefs/preferences.xhtml (1 hunks)
  • src/content/prefs/sync-configs-table.tsx (5 hunks)
  • src/content/sync/__tests__/property-builder.spec.ts (6 hunks)
  • src/content/sync/__tests__/sync-regular-item.spec.ts (1 hunks)
  • src/content/sync/property-builder.ts (4 hunks)
  • src/content/sync/sync-job.ts (4 hunks)
  • src/content/utils/elements.ts (2 hunks)
  • src/locale/en-US/notero.ftl (1 hunks)
  • types/components/virtualized-table.d.ts (1 hunks)
🔇 Additional comments (9)
src/content/sync/sync-job.ts (1)

24-24: LGTM!

The type definition for collectionRelationMap is clear and appropriate.

src/content/sync/__tests__/sync-regular-item.spec.ts (1)

72-72: LGTM!

The initialization of collectionRelationMap in the test setup is appropriate.

scripts/build.ts (1)

32-41: LGTM! Build configuration follows existing patterns

The new build configuration for dialog.tsx is well-structured and consistent with other entries.

src/content/prefs/preferences.xhtml (1)

50-63: LGTM! Well-structured UI changes.

The new linked database UI elements are properly integrated with:

  • Correct usage of localization
  • Consistent styling with existing elements
  • Proper preference binding
src/locale/en-US/notero.ftl (1)

22-22: LGTM! Consistent localization entry.

The new localization entry follows the existing pattern and naming convention.

src/content/prefs/notero-pref.ts (1)

6-6: LGTM! Clean implementation of the new preference.

The addition of linkedCollectionID preference follows the established pattern and maintains type safety.

Also applies to: 41-41, 89-89

src/content/prefs/sync-configs-table.tsx (1)

26-28: LGTM! Clean column addition.

The new associatedLink column is properly integrated into the table structure.

src/content/prefs/preferences.tsx (1)

33-33: LGTM! Well-structured implementation of linked database menu.

The changes:

  1. Properly integrate the linked database menu
  2. Handle menu state correctly during database fetches
  3. Include appropriate localization
  4. Maintain consistent error handling

Also applies to: 57-59, 116-118, 122-122, 202-202, 219-219, 222-222

src/content/prefs/collection-sync-config.ts (1)

8-8: Consider adding link format validation.

Since associatedLink represents a relation in another database, it might be beneficial to validate its format to ensure it matches the expected structure.

Let's check if there are any existing link format validations in the codebase:

Comment on lines +55 to +60
const collectionRelationMap = Object.fromEntries(
Object.entries(loadSyncConfigs()).map(([collectionID, config]) => [
collectionID,
config?.associatedLink,
]),
) as Record<number, string>;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix type mismatch in collectionRelationMap creation.

There's a type inconsistency between the declared type Record<string, string> and the cast to Record<number, string>. This could lead to runtime errors.

Apply this diff to fix the type consistency:

  const collectionRelationMap = Object.fromEntries(
    Object.entries(loadSyncConfigs()).map(([collectionID, config]) => [
      collectionID,
      config?.associatedLink,
    ]),
-  ) as Record<number, string>;
+  ) as Record<string, string>;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const collectionRelationMap = Object.fromEntries(
Object.entries(loadSyncConfigs()).map(([collectionID, config]) => [
collectionID,
config?.associatedLink,
]),
) as Record<number, string>;
const collectionRelationMap = Object.fromEntries(
Object.entries(loadSyncConfigs()).map(([collectionID, config]) => [
collectionID,
config?.associatedLink,
]),
) as Record<string, string>;

@@ -180,6 +180,7 @@ describe('buildProperties', () => {
databaseProperties: {},
item,
pageTitleFormat: format,
collectionRelationMap: {},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add test coverage for new properties.

While the collectionRelationMap parameter is correctly added to all test cases, there's no test coverage for the new Link and Zotero Direct Link properties.

Consider adding these test cases:

it('returns correct relation links for collections', async () => {
  const { item } = setup();
  const collectionRelationMap = {
    '1': 'relation-1',
    '2': 'relation-2'
  };
  
  item.getCollections.mockReturnValue(['1', '2']);

  const result = await buildProperties({
    citationFormat: 'style',
    databaseProperties: {
      Link: propertyConfig('Link', 'relation'),
    },
    item,
    pageTitleFormat: PageTitleFormat.itemTitle,
    collectionRelationMap,
  });

  expect(result.Link).toEqual({
    type: 'relation',
    relation: [
      { id: 'relation-1' },
      { id: 'relation-2' }
    ]
  });
});

it('returns correct Zotero direct link', async () => {
  const { item } = setup();
  const itemURI = 'zotero://select/library/items/ITEMKEY123';
  
  vi.mocked(Zotero.URI.getItemURI).mockReturnValue(itemURI);

  const result = await buildProperties({
    citationFormat: 'style',
    databaseProperties: {
      'Zotero Direct Link': propertyConfig('Zotero Direct Link', 'url'),
    },
    item,
    pageTitleFormat: PageTitleFormat.itemTitle,
    collectionRelationMap: {},
  });

  expect(result['Zotero Direct Link']).toEqual({
    type: 'url',
    url: itemURI
  });
});

Also applies to: 204-204, 226-226, 267-267, 300-300, 321-321

Comment on lines +12 to +13
onload="notero.dialog.init();"
>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove duplicate initialization

The notero.dialog.init() function is called twice - both in window and dialog onload. Remove one of the calls to prevent potential initialization issues.

   buttons="cancel,accept"
-  onload="notero.dialog.init();"
 >
   <script>
     Services.scriptloader.loadSubScript(

Also applies to: 28-29

Comment on lines +10 to +11
style="width: 100px; height: 100px"
buttons="cancel,accept"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Adjust window dimensions

The hardcoded window dimensions (100x100) seem too small for the content. Consider:

  1. Using more appropriate dimensions
  2. Making the window resizable
  3. Using min/max dimensions instead of fixed size
-  style="width: 100px; height: 100px"
+  style="min-width: 400px; min-height: 200px"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
style="width: 100px; height: 100px"
buttons="cancel,accept"
style="min-width: 400px; min-height: 200px"
buttons="cancel,accept"

Comment on lines +43 to +60
export function setMenuItems(
menuList: XUL.MenuListElement,
items: MenuItem[],
): void {
menuList.menupopup.replaceChildren();

items.forEach(({ disabled, l10nId, label, value }) => {
const item = createXULElement(document, 'menuitem');
item.value = value;
item.disabled = Boolean(disabled);
if (l10nId) {
document.l10n.setAttributes(item, l10nId);
} else {
item.label = label || value;
}
menuList.menupopup.append(item);
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling and optimize DOM operations

The setMenuItems function needs improvements:

  1. Add null checks for menuList.menupopup
  2. Batch DOM operations for better performance
  3. Add error handling for localization
 export function setMenuItems(
   menuList: XUL.MenuListElement,
   items: MenuItem[],
 ): void {
+  if (!menuList?.menupopup) {
+    throw new Error('Invalid menuList: menupopup not found');
+  }
+
+  // Create document fragment for batch DOM update
+  const fragment = document.createDocumentFragment();
+
   menuList.menupopup.replaceChildren();
 
   items.forEach(({ disabled, l10nId, label, value }) => {
     const item = createXULElement(document, 'menuitem');
     item.value = value;
     item.disabled = Boolean(disabled);
     if (l10nId) {
-      document.l10n.setAttributes(item, l10nId);
+      try {
+        document.l10n.setAttributes(item, l10nId);
+      } catch (error) {
+        console.error(`Failed to set l10n attributes for menuitem: ${value}`, error);
+        item.label = value; // Fallback to value if l10n fails
+      }
     } else {
       item.label = label || value;
     }
-    menuList.menupopup.append(item);
+    fragment.append(item);
   });
+
+  menuList.menupopup.append(fragment);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function setMenuItems(
menuList: XUL.MenuListElement,
items: MenuItem[],
): void {
menuList.menupopup.replaceChildren();
items.forEach(({ disabled, l10nId, label, value }) => {
const item = createXULElement(document, 'menuitem');
item.value = value;
item.disabled = Boolean(disabled);
if (l10nId) {
document.l10n.setAttributes(item, l10nId);
} else {
item.label = label || value;
}
menuList.menupopup.append(item);
});
}
export function setMenuItems(
menuList: XUL.MenuListElement,
items: MenuItem[],
): void {
if (!menuList?.menupopup) {
throw new Error('Invalid menuList: menupopup not found');
}
// Create document fragment for batch DOM update
const fragment = document.createDocumentFragment();
menuList.menupopup.replaceChildren();
items.forEach(({ disabled, l10nId, label, value }) => {
const item = createXULElement(document, 'menuitem');
item.value = value;
item.disabled = Boolean(disabled);
if (l10nId) {
try {
document.l10n.setAttributes(item, l10nId);
} catch (error) {
console.error(`Failed to set l10n attributes for menuitem: ${value}`, error);
item.label = value; // Fallback to value if l10n fails
}
} else {
item.label = label || value;
}
fragment.append(item);
});
menuList.menupopup.append(fragment);
}

Comment on lines +23 to +24
/* eslint-disable @typescript-eslint/no-non-null-assertion */
this.params = window.arguments![0]! as DialogArguments;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider safer type assertions.

The non-null assertion on window.arguments could lead to runtime errors. Consider adding proper type checking.

-    this.params = window.arguments![0]! as DialogArguments;
+    if (!window.arguments?.[0]) {
+      throw new Error('Dialog initialized without arguments');
+    }
+    this.params = window.arguments[0] as DialogArguments;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/* eslint-disable @typescript-eslint/no-non-null-assertion */
this.params = window.arguments![0]! as DialogArguments;
/* eslint-disable @typescript-eslint/no-non-null-assertion */
if (!window.arguments?.[0]) {
throw new Error('Dialog initialized without arguments');
}
this.params = window.arguments[0] as DialogArguments;

Comment on lines +59 to +67
accept(): boolean {
// @ts-expect-error dataOut is not defined in the type
window.arguments![0]!.dataOut = {
associatedLink: this.associatedLinkElement.value,
syncEnabled: this.syncEnabledElement.checked,
accepted: true,
};
return true;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve type safety in the accept method.

The current implementation uses @ts-expect-error and unsafe type assertions. Consider extending the window type or using a safer approach.

+interface DialogWindow extends Window {
+  arguments: [{
+    dataOut?: DialogArguments;
+  } & DialogArguments];
+}
+
 accept(): boolean {
-    // @ts-expect-error dataOut is not defined in the type
-    window.arguments![0]!.dataOut = {
+    (window as DialogWindow).arguments[0].dataOut = {
       associatedLink: this.associatedLinkElement.value,
       syncEnabled: this.syncEnabledElement.checked,
       accepted: true,
     };
     return true;
   }

Committable suggestion skipped: line range outside the PR's diff.

src/content/prefs/dialog.tsx Show resolved Hide resolved
Comment on lines +47 to +54
// @ts-expect-error - `Zotero.Notero` is not defined in the Zotero namespace
const aomStartup = Components.classes[
'@mozilla.org/addons/addon-manager-startup;1'
].getService(Components.interfaces.amIAddonManagerStartup);
const manifestURI = Services.io.newURI(rootURI + 'manifest.json');
chromeHandle = aomStartup.registerChrome(manifestURI, [
['content', 'notero', rootURI + 'content/'],
]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for chrome registration.

The chrome registration process should include error handling to ensure proper initialization.

-  const aomStartup = Components.classes[
-    '@mozilla.org/addons/addon-manager-startup;1'
-  ].getService(Components.interfaces.amIAddonManagerStartup);
-  const manifestURI = Services.io.newURI(rootURI + 'manifest.json');
-  chromeHandle = aomStartup.registerChrome(manifestURI, [
-    ['content', 'notero', rootURI + 'content/'],
-  ]);
+  try {
+    const aomStartup = Components.classes[
+      '@mozilla.org/addons/addon-manager-startup;1'
+    ].getService(Components.interfaces.amIAddonManagerStartup);
+    const manifestURI = Services.io.newURI(rootURI + 'manifest.json');
+    chromeHandle = aomStartup.registerChrome(manifestURI, [
+      ['content', 'notero', rootURI + 'content/'],
+    ]);
+    log('Chrome registration successful');
+  } catch (error) {
+    log(`Chrome registration failed: ${error}`);
+    throw error;
+  }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +154 to +183
private openCollectionDialog(
collection: Zotero.Collection,
syncEnabled: boolean,
associatedLink: string | undefined,
) {
const params = {
associatedLink: associatedLink || '',
syncEnabled,
accepted: false,
dataOut: null,
};

// create zotero window to add
const window = getMainWindow();
window.openDialog(
'chrome://notero/content/prefs/dialog.xhtml',
'',
'chrome,modal,centerscreen',
params,
);
const data = params.dataOut as unknown as
| { accepted: boolean; associatedLink: string; syncEnabled: boolean }
| undefined;

if (!data || !data.accepted) {
return null;
}

return data;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve dialog implementation and type safety.

  1. The type casting of dialog result is unsafe and could lead to runtime errors.
  2. Consider using a type guard or validation for the dialog result.
  3. Add error handling for dialog failures.
-    const data = params.dataOut as unknown as
-      | { accepted: boolean; associatedLink: string; syncEnabled: boolean }
-      | undefined;
+    interface DialogResult {
+      accepted: boolean;
+      associatedLink: string;
+      syncEnabled: boolean;
+    }
+
+    function isValidDialogResult(data: unknown): data is DialogResult {
+      return (
+        typeof data === 'object' &&
+        data !== null &&
+        'accepted' in data &&
+        'associatedLink' in data &&
+        'syncEnabled' in data &&
+        typeof (data as DialogResult).accepted === 'boolean' &&
+        typeof (data as DialogResult).associatedLink === 'string' &&
+        typeof (data as DialogResult).syncEnabled === 'boolean'
+      );
+    }
+
+    try {
+      const data = params.dataOut;
+      if (!isValidDialogResult(data)) {
+        logger.warn('Invalid dialog result:', data);
+        return null;
+      }
+      return data;
+    } catch (error) {
+      logger.error('Error processing dialog result:', error);
+      return null;
+    }

Committable suggestion skipped: line range outside the PR's diff.

@auscyber
Copy link
Contributor Author

btw a lot of these code rabbit responses are bit rubbish as it has no idea how the code works

@auscyber
Copy link
Contributor Author

additionally, as a consequences you can no longer select multiple at once to sync, but at the ability to link with a relation, it seems worth it imo. because creating some sort of link to a relation allows you to do complex filtering in formulas which was previously unavailable due to there not being a link between this database and others that was calculated at import time (other than if you used an automation)

an appropriate use case for this (which i designed this for)
is for linking collections with classes so I can link texts and sources to subjects and then to specific classes and specific weeks. very neat use for a student

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant