Skip to content

Conversation

@kamilkosek
Copy link
Contributor

@kamilkosek kamilkosek commented Sep 16, 2025

grafik

Summary by CodeRabbit

  • New Features
    • YAML editor now provides context-aware autocomplete for parentId and id fields, suggesting relevant libraries and collections from your server.
    • Suggestions load dynamically as needed, appear as you type, and replace text accurately within the current line.
    • Automatically enabled when opening the editor and cleaned up on close for a smoother, more reliable experience.
    • Improves accuracy and speed when referencing the correct library or collection without leaving the editor.

@lostb1t lostb1t requested a review from Copilot September 16, 2025 08:46
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds dynamic intellisense support for parentId and id values in the YAML editor by fetching libraries and collections from the Jellyfin API and providing auto-completion suggestions.

  • Adds functionality to fetch libraries and collections from Jellyfin API for auto-completion
  • Implements Monaco Editor completion provider for parentId/id fields in YAML
  • Integrates the completion provider with proper cleanup on component disposal

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@lostb1t
Copy link
Contributor

lostb1t commented Oct 8, 2025

@kamilkosek if you handle copilots comments ill merge

@kamilkosek
Copy link
Contributor Author

@kamilkosek if you handle copilots comments ill merge

thanks for reminding me :D Was on my todo list - but I am currently stuck in another project so streamyfin stuff gets a bit neglected.

coderabbitai[bot]
coderabbitai bot previously requested changes Oct 8, 2025
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: 1

🧹 Nitpick comments (4)
Jellyfin.Plugin.Streamyfin/Pages/YamlEditor/index.js (4)

58-77: Improve suggestion matching and ordering

Enhance UX by letting users match by name or id and keep libraries before collections.

-                        const libSuggestions = libraries
+                        const libSuggestions = libraries
                             .filter(i => i?.Id && i?.Name)
                             .map(i => ({
                                 label: `${i.Name} (${i.Id})`,
                                 kind: monaco.languages.CompletionItemKind.Value,
                                 insertText: i.Id,
+                                filterText: `${i.Name} ${i.Id}`,
+                                sortText: `1-${i.Name}`,
                                 detail: 'Library folder',
                                 documentation: i.Path ? `Path: ${i.Path}` : undefined
                             }));
@@
-                        const colSuggestions = collections
+                        const colSuggestions = collections
                             .filter(i => i?.Id && i?.Name)
                             .map(i => ({
                                 label: `${i.Name} (${i.Id})`,
                                 kind: monaco.languages.CompletionItemKind.Value,
                                 insertText: i.Id,
+                                filterText: `${i.Name} ${i.Id}`,
+                                sortText: `2-${i.Name}`,
                                 detail: 'Collection',
                                 documentation: i.Overview || undefined
                             }));

96-99: ‘id’ is very generic — consider scoping to avoid over-triggering

Matching any key named id can surface suggestions in unrelated contexts. Prefer limiting to parentId or gating id by surrounding context (e.g., list items under known sections), or make id support optional/toggled.


105-123: Expand replacement range to include the whole current value (avoid leftovers/extra quotes)

Currently only replaces up to the cursor, which can leave trailing characters or closing quotes. Compute the end column to cover the full current value (respecting quotes) before replacing.

-                                // Compute replacement range: from word start to cursor
-                                const word = model.getWordUntilPosition(position);
-                                const startColFromColon = (() => {
-                                    const idx = beforeCursor.lastIndexOf(':');
-                                    if (idx === -1) return word.startColumn;
-                                    let start = idx + 1; // first char after colon
-                                    // skip spaces
-                                    while (start < beforeCursor.length && beforeCursor.charAt(start) === ' ') start++;
-                                    // skip optional opening quotes
-                                    while (start < beforeCursor.length && (beforeCursor.charAt(start) === '"' || beforeCursor.charAt(start) === "'")) start++;
-                                    // Monaco columns are 1-based
-                                    return start + 1;
-                                })();
-                                const range = new monaco.Range(
-                                    position.lineNumber,
-                                    Math.max(1, startColFromColon),
-                                    position.lineNumber,
-                                    position.column
-                                );
+                                // Compute replacement range: from value start to end-of-value
+                                const word = model.getWordUntilPosition(position);
+                                const startInfo = (() => {
+                                    const idx = beforeCursor.lastIndexOf(':');
+                                    if (idx === -1) return { startOffset: position.column - 1, startColumn: word.startColumn };
+                                    let start = idx + 1; // first char after colon (0-based)
+                                    while (start < beforeCursor.length && beforeCursor.charAt(start) === ' ') start++; // spaces
+                                    const hadQuote = beforeCursor.charAt(start) === '"' || beforeCursor.charAt(start) === "'";
+                                    if (hadQuote) start++; // skip opening quote
+                                    return { startOffset: start, startColumn: start + 1, hadQuote };
+                                })();
+                                const afterCursor = line.substring(position.column - 1);
+                                const endColumn = (() => {
+                                    let end = position.column - 1; // 0-based
+                                    const quoteChar = startInfo.hadQuote
+                                        ? beforeCursor.charAt(startInfo.startOffset - 1)
+                                        : null;
+                                    if (quoteChar) {
+                                        for (let i = 0; i < afterCursor.length; i++) {
+                                            const ch = afterCursor.charAt(i);
+                                            if (ch === quoteChar || ch === '#' || ch === '\r' || ch === '\n') {
+                                                if (ch === quoteChar) end++; // include closing quote
+                                                break;
+                                            }
+                                            end++;
+                                        }
+                                    } else {
+                                        for (let i = 0; i < afterCursor.length; i++) {
+                                            const ch = afterCursor.charAt(i);
+                                            if (/\s|["'#:,]/.test(ch)) break;
+                                            end++;
+                                        }
+                                    }
+                                    return end + 1; // to 1-based
+                                })();
+                                const range = new monaco.Range(
+                                    position.lineNumber,
+                                    Math.max(1, startInfo.startColumn),
+                                    position.lineNumber,
+                                    Math.max(position.column, endColumn)
+                                );

220-224: Also reset cached suggestions on hide to avoid stale data and free memory

Resetting lets you refetch after user/library changes in the same app session.

                 Page?.parentIdProvider?.dispose?.()
                 Page.editor = undefined;
                 Page.yaml = undefined;
                 Page.parentIdProvider = undefined;
+                Page.parentIdSuggestions = undefined;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63bfaad and b2a0f16.

📒 Files selected for processing (1)
  • Jellyfin.Plugin.Streamyfin/Pages/YamlEditor/index.js (3 hunks)
🔇 Additional comments (1)
Jellyfin.Plugin.Streamyfin/Pages/YamlEditor/index.js (1)

191-193: Nice integration of the provider into init and lifecycle

Registering in init and disposing on viewhide keeps things clean.

Comment on lines +27 to +33
const userId = await window.ApiClient.getCurrentUserId?.() ?? null;

// Build URLs using ApiClient to preserve base path and auth
// Prefer user views over raw media folders for broader compatibility
const libsUrl = userId
? window.ApiClient.getUrl(`Users/${userId}/Views`)
: window.ApiClient.getUrl('Library/MediaFolders');
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make await ?? precedence explicit and allow retries after load failure

  • Parenthesize await … ?? null to avoid precedence gotchas.
  • On fetch failure you set parentIdSuggestions = []. That prevents future retries in the same session because the provider checks only for Array-ness. Use undefined to re-attempt later.
-                        const userId = await window.ApiClient.getCurrentUserId?.() ?? null;
+                        const userId = (await window.ApiClient.getCurrentUserId?.()) ?? null;
@@
-                        console.warn('Failed to load parentId suggestions', e);
-                        Page.parentIdSuggestions = [];
+                        console.warn('Failed to load parentId suggestions', e);
+                        // unset to allow retry on next invocation
+                        Page.parentIdSuggestions = undefined;

Also applies to: 80-83

🤖 Prompt for AI Agents
In Jellyfin.Plugin.Streamyfin/Pages/YamlEditor/index.js around lines 27-33 (and
also apply the same change at lines 80-83), make the await/nullish-coalescing
precedence explicit by parenthesizing the awaited call so the nullish coalescing
applies to the awaited result (i.e., wrap the await expression in parentheses
before applying ?? null), and when a fetch fails do not set parentIdSuggestions
to an empty array (which prevents retries) but set it to undefined so the
provider can attempt loading again later; apply both changes in the two
mentioned locations.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 3 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

console.log("Hiding")
Page?.editor?.dispose()
Page?.yaml?.dispose()
Page?.parentIdProvider?.dispose?.()
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

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

The optional chaining on dispose suggests uncertainty about the method's existence. Consider checking if dispose is a function before calling it: if (typeof Page?.parentIdProvider?.dispose === 'function') Page.parentIdProvider.dispose()

Suggested change
Page?.parentIdProvider?.dispose?.()
if (typeof Page?.parentIdProvider?.dispose === 'function') Page.parentIdProvider.dispose();

Copilot uses AI. Check for mistakes.
@lostb1t lostb1t merged commit ec2059e into streamyfin:main Oct 8, 2025
3 checks passed
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