mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Navigation: Show list of pinned items on MegaMenu (#90280)
* Navigation: Show list of pinned ites on the navigation * Rename section to 'Bookmarks' * Internationalization * Rename everything to bookmarks * Update public/app/core/reducers/navBarTree.ts Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * Ignore empty message as well * Dont update navigation if there is an error patching --------- Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>
This commit is contained in:
parent
38ac0f3506
commit
546f4aa700
@ -53,7 +53,7 @@ Content-Type: application/json
|
|||||||
"timezone": "utc",
|
"timezone": "utc",
|
||||||
"weekStart": "",
|
"weekStart": "",
|
||||||
"navbar": {
|
"navbar": {
|
||||||
"savedItemIds": null
|
"bookmarkIds": null
|
||||||
},
|
},
|
||||||
"queryHistory": {
|
"queryHistory": {
|
||||||
"homeTab": ""
|
"homeTab": ""
|
||||||
@ -142,7 +142,7 @@ Content-Type: application/json
|
|||||||
"timezone": "",
|
"timezone": "",
|
||||||
"weekStart": "",
|
"weekStart": "",
|
||||||
"navbar": {
|
"navbar": {
|
||||||
"savedItemIds": null
|
"bookmarkIds": null
|
||||||
},
|
},
|
||||||
"queryHistory": {
|
"queryHistory": {
|
||||||
"homeTab": ""
|
"homeTab": ""
|
||||||
|
@ -49,7 +49,7 @@ lineage: schemas: [{
|
|||||||
} @cuetsy(kind="interface")
|
} @cuetsy(kind="interface")
|
||||||
|
|
||||||
#NavbarPreference: {
|
#NavbarPreference: {
|
||||||
savedItemIds: [...string]
|
bookmarkIds: [...string]
|
||||||
} @cuetsy(kind="interface")
|
} @cuetsy(kind="interface")
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
@ -22,11 +22,11 @@ export interface CookiePreferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface NavbarPreference {
|
export interface NavbarPreference {
|
||||||
savedItemIds: Array<string>;
|
bookmarkIds: Array<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultNavbarPreference: Partial<NavbarPreference> = {
|
export const defaultNavbarPreference: Partial<NavbarPreference> = {
|
||||||
savedItemIds: [],
|
bookmarkIds: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,7 +18,7 @@ type CookiePreferences struct {
|
|||||||
|
|
||||||
// NavbarPreference defines model for NavbarPreference.
|
// NavbarPreference defines model for NavbarPreference.
|
||||||
type NavbarPreference struct {
|
type NavbarPreference struct {
|
||||||
SavedItemIds []string `json:"savedItemIds"`
|
BookmarkIds []string `json:"bookmarkIds"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryHistoryPreference defines model for QueryHistoryPreference.
|
// QueryHistoryPreference defines model for QueryHistoryPreference.
|
||||||
|
@ -12,6 +12,7 @@ const (
|
|||||||
// any items with default weight.
|
// any items with default weight.
|
||||||
|
|
||||||
WeightHome = (iota - 20) * 100
|
WeightHome = (iota - 20) * 100
|
||||||
|
WeightBookmarks
|
||||||
WeightSavedItems
|
WeightSavedItems
|
||||||
WeightDashboard
|
WeightDashboard
|
||||||
WeightExplore
|
WeightExplore
|
||||||
|
@ -163,6 +163,20 @@ func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, prefs *pref.Prefere
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagPinNavItems) {
|
||||||
|
bookmarks := s.buildBookmarksNavLinks(prefs, treeRoot)
|
||||||
|
|
||||||
|
treeRoot.AddSection(&navtree.NavLink{
|
||||||
|
Text: "Bookmarks",
|
||||||
|
Id: "bookmarks",
|
||||||
|
Icon: "bookmark",
|
||||||
|
SortWeight: navtree.WeightBookmarks,
|
||||||
|
Children: bookmarks,
|
||||||
|
EmptyMessageId: "bookmarks-empty",
|
||||||
|
Url: s.cfg.AppSubURL + "/bookmarks",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return treeRoot, nil
|
return treeRoot, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,6 +330,38 @@ func (s *ServiceImpl) buildStarredItemsNavLinks(c *contextmodel.ReqContext) ([]*
|
|||||||
|
|
||||||
return starredItemsChildNavs, nil
|
return starredItemsChildNavs, nil
|
||||||
}
|
}
|
||||||
|
func (s *ServiceImpl) buildBookmarksNavLinks(prefs *pref.Preference, treeRoot *navtree.NavTreeRoot) []*navtree.NavLink {
|
||||||
|
bookmarksChildNavs := []*navtree.NavLink{}
|
||||||
|
|
||||||
|
bookmarkIds := prefs.JSONData.Navbar.BookmarkIds
|
||||||
|
|
||||||
|
if len(bookmarkIds) > 0 {
|
||||||
|
for _, id := range bookmarkIds {
|
||||||
|
item := treeRoot.FindById(id)
|
||||||
|
if item != nil {
|
||||||
|
bookmarksChildNavs = append(bookmarksChildNavs, &navtree.NavLink{
|
||||||
|
Id: item.Id,
|
||||||
|
Text: item.Text,
|
||||||
|
SubTitle: item.SubTitle,
|
||||||
|
Icon: item.Icon,
|
||||||
|
Img: item.Img,
|
||||||
|
Url: item.Url,
|
||||||
|
Target: item.Target,
|
||||||
|
HideFromTabs: item.HideFromTabs,
|
||||||
|
RoundIcon: item.RoundIcon,
|
||||||
|
IsSection: item.IsSection,
|
||||||
|
HighlightText: item.HighlightText,
|
||||||
|
HighlightID: item.HighlightID,
|
||||||
|
PluginID: item.PluginID,
|
||||||
|
IsCreateAction: item.IsCreateAction,
|
||||||
|
Keywords: item.Keywords,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bookmarksChildNavs
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext) []*navtree.NavLink {
|
func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext) []*navtree.NavLink {
|
||||||
hasAccess := ac.HasAccess(s.accessControl, c)
|
hasAccess := ac.HasAccess(s.accessControl, c)
|
||||||
|
@ -98,7 +98,7 @@ type QueryHistoryPreference struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NavbarPreference struct {
|
type NavbarPreference struct {
|
||||||
SavedItemIds []string `json:"savedItemIds"`
|
BookmarkIds []string `json:"bookmarkIds"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *PreferenceJSONData) FromDB(data []byte) error {
|
func (j *PreferenceJSONData) FromDB(data []byte) error {
|
||||||
|
@ -97,11 +97,11 @@ func GetPreferencesFor(ctx context.Context,
|
|||||||
dto.Language = &preference.JSONData.Language
|
dto.Language = &preference.JSONData.Language
|
||||||
}
|
}
|
||||||
|
|
||||||
if preference.JSONData.Navbar.SavedItemIds != nil {
|
if preference.JSONData.Navbar.BookmarkIds != nil {
|
||||||
dto.Navbar = &preferences.NavbarPreference{
|
dto.Navbar = &preferences.NavbarPreference{
|
||||||
SavedItemIds: []string{},
|
BookmarkIds: []string{},
|
||||||
}
|
}
|
||||||
dto.Navbar.SavedItemIds = preference.JSONData.Navbar.SavedItemIds
|
dto.Navbar.BookmarkIds = preference.JSONData.Navbar.BookmarkIds
|
||||||
}
|
}
|
||||||
|
|
||||||
if preference.JSONData.QueryHistory.HomeTab != "" {
|
if preference.JSONData.QueryHistory.HomeTab != "" {
|
||||||
|
@ -71,8 +71,8 @@ func (s *Service) GetWithDefaults(ctx context.Context, query *pref.GetPreference
|
|||||||
res.JSONData.QueryHistory.HomeTab = p.JSONData.QueryHistory.HomeTab
|
res.JSONData.QueryHistory.HomeTab = p.JSONData.QueryHistory.HomeTab
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.JSONData.Navbar.SavedItemIds != nil {
|
if p.JSONData.Navbar.BookmarkIds != nil {
|
||||||
res.JSONData.Navbar.SavedItemIds = p.JSONData.Navbar.SavedItemIds
|
res.JSONData.Navbar.BookmarkIds = p.JSONData.Navbar.BookmarkIds
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.JSONData.CookiePreferences != nil {
|
if p.JSONData.CookiePreferences != nil {
|
||||||
@ -174,11 +174,11 @@ func (s *Service) Patch(ctx context.Context, cmd *pref.PatchPreferenceCommand) e
|
|||||||
preference.JSONData.Language = *cmd.Language
|
preference.JSONData.Language = *cmd.Language
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.Navbar != nil && cmd.Navbar.SavedItemIds != nil {
|
if cmd.Navbar != nil && cmd.Navbar.BookmarkIds != nil {
|
||||||
if preference.JSONData == nil {
|
if preference.JSONData == nil {
|
||||||
preference.JSONData = &pref.PreferenceJSONData{}
|
preference.JSONData = &pref.PreferenceJSONData{}
|
||||||
}
|
}
|
||||||
preference.JSONData.Navbar.SavedItemIds = cmd.Navbar.SavedItemIds
|
preference.JSONData.Navbar.BookmarkIds = cmd.Navbar.BookmarkIds
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.QueryHistory != nil {
|
if cmd.QueryHistory != nil {
|
||||||
|
@ -17050,7 +17050,7 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "NavbarPreference defines model for NavbarPreference.",
|
"title": "NavbarPreference defines model for NavbarPreference.",
|
||||||
"properties": {
|
"properties": {
|
||||||
"savedItemIds": {
|
"bookmarkIds": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
@ -3,14 +3,15 @@ import { DOMAttributes } from '@react-types/shared';
|
|||||||
import { memo, forwardRef, useCallback } from 'react';
|
import { memo, forwardRef, useCallback } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { config, reportInteraction } from '@grafana/runtime';
|
import { config, reportInteraction } from '@grafana/runtime';
|
||||||
import { CustomScrollbar, Icon, IconButton, useStyles2, Stack } from '@grafana/ui';
|
import { CustomScrollbar, Icon, IconButton, useStyles2, Stack } from '@grafana/ui';
|
||||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||||
import { t } from 'app/core/internationalization';
|
import { t } from 'app/core/internationalization';
|
||||||
|
import { setBookmark } from 'app/core/reducers/navBarTree';
|
||||||
import { usePatchUserPreferencesMutation } from 'app/features/preferences/api/index';
|
import { usePatchUserPreferencesMutation } from 'app/features/preferences/api/index';
|
||||||
import { useSelector } from 'app/types';
|
import { useDispatch, useSelector } from 'app/types';
|
||||||
|
|
||||||
import { MegaMenuItem } from './MegaMenuItem';
|
import { MegaMenuItem } from './MegaMenuItem';
|
||||||
import { usePinnedItems } from './hooks';
|
import { usePinnedItems } from './hooks';
|
||||||
@ -28,6 +29,7 @@ export const MegaMenu = memo(
|
|||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { chrome } = useGrafana();
|
const { chrome } = useGrafana();
|
||||||
|
const dispatch = useDispatch();
|
||||||
const state = chrome.useState();
|
const state = chrome.useState();
|
||||||
const [patchPreferences] = usePatchUserPreferencesMutation();
|
const [patchPreferences] = usePatchUserPreferencesMutation();
|
||||||
const pinnedItems = usePinnedItems();
|
const pinnedItems = usePinnedItems();
|
||||||
@ -61,7 +63,8 @@ export const MegaMenu = memo(
|
|||||||
[pinnedItems]
|
[pinnedItems]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onPinItem = (id?: string) => {
|
const onPinItem = (item: NavModelItem) => {
|
||||||
|
const id = item.id;
|
||||||
if (id && config.featureToggles.pinNavItems) {
|
if (id && config.featureToggles.pinNavItems) {
|
||||||
const navItem = navTree.find((item) => item.id === id);
|
const navItem = navTree.find((item) => item.id === id);
|
||||||
const isSaved = isPinned(id);
|
const isSaved = isPinned(id);
|
||||||
@ -73,9 +76,13 @@ export const MegaMenu = memo(
|
|||||||
patchPreferences({
|
patchPreferences({
|
||||||
patchPrefsCmd: {
|
patchPrefsCmd: {
|
||||||
navbar: {
|
navbar: {
|
||||||
savedItemIds: newItems,
|
bookmarkIds: newItems,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
}).then((data) => {
|
||||||
|
if (!data.error) {
|
||||||
|
dispatch(setBookmark({ item: item, isSaved: !isSaved }));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -19,7 +19,7 @@ interface Props {
|
|||||||
activeItem?: NavModelItem;
|
activeItem?: NavModelItem;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
level?: number;
|
level?: number;
|
||||||
onPin: (id?: string) => void;
|
onPin: (item: NavModelItem) => void;
|
||||||
isPinned: (id?: string) => boolean;
|
isPinned: (id?: string) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ export function MegaMenuItem({ link, activeItem, level = 0, onClick, onPin, isPi
|
|||||||
target={link.target}
|
target={link.target}
|
||||||
url={link.url}
|
url={link.url}
|
||||||
id={link.id}
|
id={link.id}
|
||||||
onPin={onPin}
|
onPin={() => onPin(link)}
|
||||||
isPinned={isPinned(link.id)}
|
isPinned={isPinned(link.id)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
@ -32,7 +32,8 @@ export function MegaMenuItemText({ children, isActive, onClick, target, url, id,
|
|||||||
}
|
}
|
||||||
{config.featureToggles.pinNavItems && (
|
{config.featureToggles.pinNavItems && (
|
||||||
<Icon
|
<Icon
|
||||||
name={isPinned ? 'favorite' : 'star'}
|
name="bookmark"
|
||||||
|
type={isPinned ? 'solid' : 'default'}
|
||||||
className={'pin-icon'}
|
className={'pin-icon'}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -5,7 +5,7 @@ import { useGetUserPreferencesQuery } from 'app/features/preferences/api';
|
|||||||
|
|
||||||
export const usePinnedItems = () => {
|
export const usePinnedItems = () => {
|
||||||
const preferences = useGetUserPreferencesQuery();
|
const preferences = useGetUserPreferencesQuery();
|
||||||
const pinnedItems = useMemo(() => preferences.data?.navbar?.savedItemIds || [], [preferences]);
|
const pinnedItems = useMemo(() => preferences.data?.navbar?.bookmarkIds || [], [preferences]);
|
||||||
|
|
||||||
if (config.featureToggles.pinNavItems) {
|
if (config.featureToggles.pinNavItems) {
|
||||||
return pinnedItems;
|
return pinnedItems;
|
||||||
|
@ -51,6 +51,31 @@ const navTreeSlice = createSlice({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setBookmark: (state, action: PayloadAction<{ item: NavModelItem; isSaved: boolean }>) => {
|
||||||
|
if (!config.featureToggles.pinNavItems) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const bookmarks = state.find((navItem) => navItem.id === 'bookmarks');
|
||||||
|
const { item, isSaved } = action.payload;
|
||||||
|
if (bookmarks) {
|
||||||
|
if (isSaved) {
|
||||||
|
if (!bookmarks.children) {
|
||||||
|
bookmarks.children = [];
|
||||||
|
}
|
||||||
|
const newBookmark: NavModelItem = {
|
||||||
|
...item,
|
||||||
|
// Clear the children, sortWeight and empty message of the item
|
||||||
|
children: [],
|
||||||
|
sortWeight: 0,
|
||||||
|
emptyMessageId: '',
|
||||||
|
emptyMessage: '',
|
||||||
|
};
|
||||||
|
bookmarks.children.push(newBookmark);
|
||||||
|
} else {
|
||||||
|
bookmarks.children = bookmarks.children?.filter((i) => i.id !== item.id) ?? [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
updateDashboardName: (state, action: PayloadAction<{ id: string; title: string; url: string }>) => {
|
updateDashboardName: (state, action: PayloadAction<{ id: string; title: string; url: string }>) => {
|
||||||
const { id, title, url } = action.payload;
|
const { id, title, url } = action.payload;
|
||||||
const starredItems = state.find((navItem) => navItem.id === 'starred');
|
const starredItems = state.find((navItem) => navItem.id === 'starred');
|
||||||
@ -73,5 +98,5 @@ const navTreeSlice = createSlice({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { setStarred, removePluginFromNavTree, updateDashboardName } = navTreeSlice.actions;
|
export const { setStarred, removePluginFromNavTree, updateDashboardName, setBookmark } = navTreeSlice.actions;
|
||||||
export const navTreeReducer = navTreeSlice.reducer;
|
export const navTreeReducer = navTreeSlice.reducer;
|
||||||
|
@ -22,6 +22,10 @@ export function getNavTitle(navId: string | undefined) {
|
|||||||
return t('nav.create-import.title', 'Import dashboard');
|
return t('nav.create-import.title', 'Import dashboard');
|
||||||
case 'alert':
|
case 'alert':
|
||||||
return t('nav.create-alert.title', 'New alert rule');
|
return t('nav.create-alert.title', 'New alert rule');
|
||||||
|
case 'bookmarks':
|
||||||
|
return t('nav.bookmarks.title', 'Bookmarks');
|
||||||
|
case 'bookmarks-empty':
|
||||||
|
return t('nav.bookmarks-empty.title', 'Bookmark pages for them to appear here');
|
||||||
case 'starred':
|
case 'starred':
|
||||||
return t('nav.starred.title', 'Starred');
|
return t('nav.starred.title', 'Starred');
|
||||||
case 'starred-empty':
|
case 'starred-empty':
|
||||||
|
@ -38,7 +38,7 @@ export type CookiePreferencesDefinesModelForCookiePreferences = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
export type NavbarPreferenceDefinesModelForNavbarPreference = {
|
export type NavbarPreferenceDefinesModelForNavbarPreference = {
|
||||||
savedItemIds?: string[];
|
bookmarkIds?: string[];
|
||||||
};
|
};
|
||||||
export type QueryHistoryPreferenceDefinesModelForQueryHistoryPreference = {
|
export type QueryHistoryPreferenceDefinesModelForQueryHistoryPreference = {
|
||||||
/** HomeTab one of: '' | 'query' | 'starred'; */
|
/** HomeTab one of: '' | 'query' | 'starred'; */
|
||||||
@ -66,7 +66,7 @@ export type ErrorResponseBody = {
|
|||||||
/** a human readable version of the error */
|
/** a human readable version of the error */
|
||||||
message: string;
|
message: string;
|
||||||
/** Status An optional status to denote the cause of the error.
|
/** Status An optional status to denote the cause of the error.
|
||||||
|
|
||||||
For example, a 412 Precondition Failed error may include additional information of why that error happened. */
|
For example, a 412 Precondition Failed error may include additional information of why that error happened. */
|
||||||
status?: string;
|
status?: string;
|
||||||
};
|
};
|
||||||
|
@ -514,6 +514,10 @@ export function getAppRoutes(): RouteDescriptor[] {
|
|||||||
() => import(/* webpackChunkName: "DataTrailsPage"*/ 'app/features/trails/DataTrailsPage')
|
() => import(/* webpackChunkName: "DataTrailsPage"*/ 'app/features/trails/DataTrailsPage')
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/bookmarks',
|
||||||
|
component: () => <NavLandingPage navId="bookmarks" />,
|
||||||
|
},
|
||||||
...getPluginCatalogRoutes(),
|
...getPluginCatalogRoutes(),
|
||||||
...getSupportBundleRoutes(),
|
...getSupportBundleRoutes(),
|
||||||
...getAlertingRoutes(),
|
...getAlertingRoutes(),
|
||||||
|
@ -1188,6 +1188,12 @@
|
|||||||
"authentication": {
|
"authentication": {
|
||||||
"title": "Authentication"
|
"title": "Authentication"
|
||||||
},
|
},
|
||||||
|
"bookmarks": {
|
||||||
|
"title": "Bookmarks"
|
||||||
|
},
|
||||||
|
"bookmarks-empty": {
|
||||||
|
"title": "Bookmark pages for them to appear here"
|
||||||
|
},
|
||||||
"collector": {
|
"collector": {
|
||||||
"title": "Collector"
|
"title": "Collector"
|
||||||
},
|
},
|
||||||
|
@ -1188,6 +1188,12 @@
|
|||||||
"authentication": {
|
"authentication": {
|
||||||
"title": "Åūŧĥęʼnŧįčäŧįőʼn"
|
"title": "Åūŧĥęʼnŧįčäŧįőʼn"
|
||||||
},
|
},
|
||||||
|
"bookmarks": {
|
||||||
|
"title": "ßőőĸmäřĸş"
|
||||||
|
},
|
||||||
|
"bookmarks-empty": {
|
||||||
|
"title": "ßőőĸmäřĸ päģęş ƒőř ŧĥęm ŧő äppęäř ĥęřę"
|
||||||
|
},
|
||||||
"collector": {
|
"collector": {
|
||||||
"title": "Cőľľęčŧőř"
|
"title": "Cőľľęčŧőř"
|
||||||
},
|
},
|
||||||
|
@ -7146,7 +7146,7 @@
|
|||||||
},
|
},
|
||||||
"NavbarPreference": {
|
"NavbarPreference": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"savedItemIds": {
|
"bookmarkIds": {
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user