Skip to content

Conversation

@lacostenycoder
Copy link
Owner

@lacostenycoder lacostenycoder commented Apr 27, 2025

Chrome browser sync was not straightforward to implement, however this approach allows users to import/export their shortcuts using a simple json file format. Easy, and just works.

Summary by Sourcery

Add functionality to import and export user shortcuts using JSON files.

New Features:

  • Allow users to export their shortcuts to a JSON file.
  • Allow users to import shortcuts from a JSON file, with options to handle conflicts (keep existing, overwrite, or merge).

Enhancements:

  • Add 'Export' and 'Import' buttons to the shortcuts list interface.
  • Filter internal metadata keys from the displayed and exported shortcut list.

Chores:

  • Include Bootstrap CSS for styling UI elements.

@sourcery-ai
Copy link

sourcery-ai bot commented Apr 27, 2025

Reviewer's Guide by Sourcery

This pull request adds functionality to export and import user shortcuts from/to a JSON file. It includes UI elements for triggering these actions and implements logic to handle conflicts during the import process.

No diagrams generated as the changes look simple and do not need a visual representation.

File-Level Changes

Change Details Files
Implement shortcut export functionality.
  • Filter out metadata keys from stored data
  • Generate JSON string from filtered data
  • Create and trigger download of JSON file
lib/list.js
Implement shortcut import functionality with conflict handling.
  • Read and parse JSON file content
  • Prompt user for import conflict resolution mode
  • Implement logic for adding new shortcuts
  • Implement logic for overwriting existing shortcuts based on mode
  • Implement logic for merging conflicting shortcuts by renaming
  • Display import summary and reload page after import
lib/list.js
Add Export and Import buttons to the UI.
  • Create Export and Import buttons
  • Add event listeners to trigger export/import functions
  • Append buttons to the page
lib/list.js
Refactor shortcut listing and event binding to exclude metadata keys.
  • Filter stored keys to exclude internal metadata
  • Use filtered keys when listing shortcuts in the UI
  • Use filtered keys when attaching event listeners to shortcut actions
lib/list.js

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @lacostenycoder - I've reviewed your changes - here's some feedback:

Overall Comments:

  • Consider updating the title or description to clarify that this feature implements manual JSON import/export rather than native browser sync.
  • The logic for filtering localStorage keys before processing is duplicated; consider extracting it into a reusable helper function.
  • Consider managing the new CSS dependency (bootstrap.min.css) through a package manager or CDN instead of adding the file directly to the repository.
Here's what I looked at during the review
  • 🟡 General issues: 1 issue found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟡 Complexity: 1 issue found
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

function exportShortcuts() {
// Create an object to hold all shortcuts
var shortcuts = {};
for (var i = 0; i < localStorage.length; i++) {
Copy link

Choose a reason for hiding this comment

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

suggestion: Consider reusing the storedKeys array for exporting shortcuts.

Since you already filter and store the shortcut keys in the storedKeys variable for display, using it in exportShortcuts would remove duplicate filtering logic and ensure consistency between displayed and exported shortcuts.

Suggested implementation:

	// Function to export shortcuts to a JSON file
	function exportShortcuts() {
		// Create an object to hold all shortcuts
		var shortcuts = {};
		storedKeys.forEach(function(key) {
			shortcuts[key] = localStorage[key];
		});

Ensure that the storedKeys array is defined and available in the scope where exportShortcuts is declared.

key !== 'deletedShortcuts') {
var url = localStorage[key];
shortcuts[key] = url;
}
Copy link

Choose a reason for hiding this comment

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

issue (complexity): Consider refactoring the filtering and conflict-resolution logic into dedicated helper functions to improve code readability and maintainability.

Refactor the filtering and conflict-resolution logic into dedicated helper functions to reduce nesting and duplicate code without changing functionality.

For example, extract the duplicate key filtering into a helper:
```js
function isShortcutKey(key) {
  return key !== 'lastSynced' &&
         !key.startsWith('lastSynced_') &&
         key !== 'shortcutsMeta' &&
         key !== 'deletedShortcuts';
}

Use this helper in both export and storedKeys generation:

var storedKeys = Object.keys(localStorage).filter(isShortcutKey);

Similarly, for the nested conflict resolution in importShortcuts, consider extracting a function:

function resolveImport(key, newUrl, importMode) {
  if (!localStorage[key]) {
    localStorage[key] = newUrl;
    return 'added';
  }
  switch (importMode) {
    case 2:
      localStorage[key] = newUrl;
      return 'updated';
    case 3:
      if (localStorage[key] !== newUrl) {
        let newKey = key + "_imported";
        let counter = 1;
        while (localStorage[newKey]) {
          newKey = key + "_imported_" + counter++;
        }
        localStorage[newKey] = newUrl;
        return 'added';
      }
      return 'skipped';
    default:
      return 'skipped';
  }
}

Then process imports like:

for (var key in shortcuts) {
  var status = resolveImport(key, shortcuts[key], importMode);
  if (status === 'added') added++;
  else if (status === 'updated') updated++;
  else skipped++;
}

These changes centralize duplicate logic and flatten nested conditionals while maintaining current behavior.

Comment on lines +2 to +8
var storedKeys = Object.keys(localStorage).filter(function(key) {
// Filter out special keys that shouldn't be displayed as shortcuts
return key !== 'lastSynced' &&
!key.startsWith('lastSynced_') &&
key !== 'shortcutsMeta' &&
key !== 'deletedShortcuts';
});
Copy link

Choose a reason for hiding this comment

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

issue (code-quality): Use const or let instead of var. (avoid-using-var)

Explanation`const` is preferred as it ensures you cannot reassign references (which can lead to buggy and confusing code). `let` may be used if you need to reassign references - it's preferred to `var` because it is block- rather than function-scoped.

From the Airbnb JavaScript Style Guide

Comment on lines +11 to +45
function exportShortcuts() {
// Create an object to hold all shortcuts
var shortcuts = {};
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
// Only export actual shortcuts, not metadata
if (key !== 'lastSynced' &&
!key.startsWith('lastSynced_') &&
key !== 'shortcutsMeta' &&
key !== 'deletedShortcuts') {
var url = localStorage[key];
shortcuts[key] = url;
}
}

var storedKeys = Object.keys(localStorage);
// Convert to JSON string
var jsonData = JSON.stringify(shortcuts, null, 2);

// Create a blob and download link
var blob = new Blob([jsonData], {type: 'application/json'});
var url = URL.createObjectURL(blob);

// Create a temporary link and trigger download
var a = document.createElement('a');
a.href = url;
a.download = 'chrome-quick-links-shortcuts.json';
document.body.appendChild(a);
a.click();

// Clean up
setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
Copy link

Choose a reason for hiding this comment

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

issue (code-quality): Avoid function declarations, favouring function assignment expressions, inside blocks. (avoid-function-declarations-in-blocks)

ExplanationFunction declarations may be hoisted in Javascript, but the behaviour is inconsistent between browsers. Hoisting is generally confusing and should be avoided. Rather than using function declarations inside blocks, you should use function expressions, which create functions in-scope.

// Function to export shortcuts to a JSON file
function exportShortcuts() {
// Create an object to hold all shortcuts
var shortcuts = {};
Copy link

Choose a reason for hiding this comment

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

issue (code-quality): Use const or let instead of var. (avoid-using-var)

Explanation`const` is preferred as it ensures you cannot reassign references (which can lead to buggy and confusing code). `let` may be used if you need to reassign references - it's preferred to `var` because it is block- rather than function-scoped.

From the Airbnb JavaScript Style Guide

} else if (importMode === 3 && localStorage[key] !== newUrl) {
// Merge mode - if URLs differ, create a new shortcut with a suffix
var newKey = key + "_imported";
var counter = 1;
Copy link

Choose a reason for hiding this comment

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

issue (code-quality): Use const or let instead of var. (avoid-using-var)

Explanation`const` is preferred as it ensures you cannot reassign references (which can lead to buggy and confusing code). `let` may be used if you need to reassign references - it's preferred to `var` because it is block- rather than function-scoped.

From the Airbnb JavaScript Style Guide

Comment on lines 163 to 172
var doDelete = function() {
var key = this.id.split('-')[1];
if ( confirm('Are you sure?') ) {
delete localStorage[key];
if(chrome.storage){
chrome.storage.sync.remove(key);
}
window.location = window.location;
}
if (confirm('Are you sure?')) {
delete localStorage[key];
if(chrome.storage){
chrome.storage.sync.remove(key);
}
window.location = window.location;
}
}
Copy link

Choose a reason for hiding this comment

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

issue (code-quality): Use const or let instead of var. (avoid-using-var)

Explanation`const` is preferred as it ensures you cannot reassign references (which can lead to buggy and confusing code). `let` may be used if you need to reassign references - it's preferred to `var` because it is block- rather than function-scoped.

From the Airbnb JavaScript Style Guide

if(chrome.storage){
chrome.storage.sync.remove(key);
}
window.location = window.location;
Copy link

Choose a reason for hiding this comment

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

suggestion (code-quality): Assigning a variable to itself has no effect. (dont-self-assign-variables)

Suggested change
window.location = window.location;
;


ExplanationAssigning a variable to itself has no effect, and is therefore either redundant or a mistake.

Comment on lines 174 to 182
function checkExample(){
var tr = document.getElementsByTagName('tr');
if(tr.length > 1) {
var example = document.getElementById('example');
example.remove();
if (example) {
example.remove();
}
}
}
Copy link

Choose a reason for hiding this comment

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

issue (code-quality): Avoid function declarations, favouring function assignment expressions, inside blocks. (avoid-function-declarations-in-blocks)

ExplanationFunction declarations may be hoisted in Javascript, but the behaviour is inconsistent between browsers. Hoisting is generally confusing and should be avoided. Rather than using function declarations inside blocks, you should use function expressions, which create functions in-scope.

var url = localStorage[key];
document.getElementById("edit-" + key).addEventListener("click", doEdit, false);
document.getElementById("delete-" + key).addEventListener("click", doDelete, false);
var editBtn = document.getElementById("edit-" + key);
Copy link

Choose a reason for hiding this comment

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

issue (code-quality): Use const or let instead of var. (avoid-using-var)

Explanation`const` is preferred as it ensures you cannot reassign references (which can lead to buggy and confusing code). `let` may be used if you need to reassign references - it's preferred to `var` because it is block- rather than function-scoped.

From the Airbnb JavaScript Style Guide

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.

2 participants