Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
N
nexpie-grafana-theme
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Registry
Registry
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kornkitt Poolsup
nexpie-grafana-theme
Commits
80a6853f
Unverified
Commit
80a6853f
authored
Oct 21, 2019
by
Andrej Ocenas
Committed by
GitHub
Oct 21, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor: Suggestion plugin for slate (#19825)
parent
3119f357
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
131 additions
and
128 deletions
+131
-128
public/app/features/explore/QueryField.tsx
+1
-5
public/app/features/explore/slate-plugins/suggestions.tsx
+130
-123
No files found.
public/app/features/explore/QueryField.tsx
View file @
80a6853f
...
...
@@ -15,8 +15,6 @@ import IndentationPlugin from './slate-plugins/indentation';
import
ClipboardPlugin
from
'./slate-plugins/clipboard'
;
import
RunnerPlugin
from
'./slate-plugins/runner'
;
import
SuggestionsPlugin
,
{
SuggestionsState
}
from
'./slate-plugins/suggestions'
;
import
{
Typeahead
}
from
'./Typeahead'
;
import
{
makeValue
,
SCHEMA
}
from
'@grafana/ui'
;
export
interface
QueryFieldProps
{
...
...
@@ -66,8 +64,6 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
mounted
:
boolean
;
runOnChangeDebounced
:
Function
;
editor
:
Editor
;
// Is required by SuggestionsPlugin
typeaheadRef
:
Typeahead
;
lastExecutedValue
:
Value
|
null
=
null
;
constructor
(
props
:
QueryFieldProps
,
context
:
Context
<
any
>
)
{
...
...
@@ -80,7 +76,7 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
// Base plugins
this
.
plugins
=
[
NewlinePlugin
(),
SuggestionsPlugin
({
onTypeahead
,
cleanText
,
portalOrigin
,
onWillApplySuggestion
,
component
:
this
}),
SuggestionsPlugin
({
onTypeahead
,
cleanText
,
portalOrigin
,
onWillApplySuggestion
}),
ClearPlugin
(),
RunnerPlugin
({
handler
:
this
.
runOnChangeAndRunQuery
}),
SelectionShortcutsPlugin
(),
...
...
public/app/features/explore/slate-plugins/suggestions.tsx
View file @
80a6853f
...
...
@@ -6,7 +6,7 @@ import { Editor as CoreEditor } from 'slate';
import
{
Plugin
as
SlatePlugin
}
from
'@grafana/slate-react'
;
import
{
TypeaheadOutput
,
CompletionItem
,
CompletionItemGroup
}
from
'app/types'
;
import
{
QueryField
,
TypeaheadInput
}
from
'../QueryField'
;
import
{
TypeaheadInput
}
from
'../QueryField'
;
import
TOKEN_MARK
from
'@grafana/ui/src/slate-plugins/slate-prism/TOKEN_MARK'
;
import
{
TypeaheadWithTheme
,
Typeahead
}
from
'../Typeahead'
;
...
...
@@ -14,6 +14,12 @@ import { makeFragment } from '@grafana/ui';
export
const
TYPEAHEAD_DEBOUNCE
=
100
;
// Commands added to the editor by this plugin.
interface
SuggestionsPluginCommands
{
selectSuggestion
:
(
suggestion
:
CompletionItem
)
=>
CoreEditor
;
applyTypeahead
:
(
suggestion
:
CompletionItem
)
=>
CoreEditor
;
}
export
interface
SuggestionsState
{
groupedItems
:
CompletionItemGroup
[];
typeaheadPrefix
:
string
;
...
...
@@ -21,28 +27,33 @@ export interface SuggestionsState {
typeaheadText
:
string
;
}
let
state
:
SuggestionsState
=
{
groupedItems
:
[],
typeaheadPrefix
:
''
,
typeaheadContext
:
''
,
typeaheadText
:
''
,
};
export
default
function
SuggestionsPlugin
({
onTypeahead
,
cleanText
,
onWillApplySuggestion
,
syntax
,
portalOrigin
,
component
,
}:
{
onTypeahead
:
(
typeahead
:
TypeaheadInput
)
=>
Promise
<
TypeaheadOutput
>
;
cleanText
?:
(
text
:
string
)
=>
string
;
onWillApplySuggestion
?:
(
suggestion
:
string
,
state
:
SuggestionsState
)
=>
string
;
syntax
?:
string
;
portalOrigin
:
string
;
component
:
QueryField
;
// Need to attach typeaheadRef here
}):
SlatePlugin
{
let
typeaheadRef
:
Typeahead
;
let
state
:
SuggestionsState
=
{
groupedItems
:
[],
typeaheadPrefix
:
''
,
typeaheadContext
:
''
,
typeaheadText
:
''
,
};
const
handleTypeaheadDebounced
=
debounce
(
handleTypeahead
,
TYPEAHEAD_DEBOUNCE
);
const
setState
=
(
update
:
Partial
<
SuggestionsState
>
)
=>
{
state
=
{
...
state
,
...
update
,
};
};
return
{
onBlur
:
(
event
,
editor
,
next
)
=>
{
state
=
{
...
...
@@ -88,7 +99,7 @@ export default function SuggestionsPlugin({
case
'ArrowUp'
:
if
(
hasSuggestions
)
{
event
.
preventDefault
();
component
.
typeaheadRef
.
moveMenuIndex
(
event
.
key
===
'ArrowDown'
?
1
:
-
1
);
typeaheadRef
.
moveMenuIndex
(
event
.
key
===
'ArrowDown'
?
1
:
-
1
);
return
;
}
...
...
@@ -98,14 +109,14 @@ export default function SuggestionsPlugin({
case
'Tab'
:
{
if
(
hasSuggestions
)
{
event
.
preventDefault
();
return
component
.
typeaheadRef
.
insertSuggestion
();
return
typeaheadRef
.
insertSuggestion
();
}
break
;
}
default
:
{
handleTypeahead
(
editor
,
onTypeahead
,
cleanText
);
handleTypeahead
Debounced
(
editor
,
setState
,
onTypeahead
,
cleanText
);
break
;
}
}
...
...
@@ -122,7 +133,7 @@ export default function SuggestionsPlugin({
// @ts-ignore
const
ed
=
editor
.
applyTypeahead
(
suggestion
);
handleTypeahead
(
editor
,
onTypeahead
,
cleanText
);
handleTypeahead
Debounced
(
editor
,
setState
,
onTypeahead
,
cleanText
);
return
ed
;
},
...
...
@@ -186,13 +197,12 @@ export default function SuggestionsPlugin({
<>
{
children
}
<
TypeaheadWithTheme
menuRef=
{
(
el
:
Typeahead
)
=>
(
component
.
typeaheadRef
=
el
)
}
menuRef=
{
(
el
:
Typeahead
)
=>
(
typeaheadRef
=
el
)
}
origin=
{
portalOrigin
}
prefix=
{
state
.
typeaheadPrefix
}
isOpen=
{
!!
state
.
groupedItems
.
length
}
groupedItems=
{
state
.
groupedItems
}
//@ts-ignore
onSelectSuggestion=
{
editor
.
selectSuggestion
}
onSelectSuggestion=
{
(
editor
as
CoreEditor
&
SuggestionsPluginCommands
).
selectSuggestion
}
/>
</>
);
...
...
@@ -200,114 +210,111 @@ export default function SuggestionsPlugin({
};
}
const
handleTypeahead
=
debounce
(
async
(
editor
:
CoreEditor
,
onTypeahead
?:
(
typeahead
:
TypeaheadInput
)
=>
Promise
<
TypeaheadOutput
>
,
cleanText
?:
(
text
:
string
)
=>
string
)
=>
{
if
(
!
onTypeahead
)
{
return
null
;
}
const
{
value
}
=
editor
;
const
{
selection
}
=
value
;
// Get decorations associated with the current line
const
parentBlock
=
value
.
document
.
getClosestBlock
(
value
.
focusBlock
.
key
);
const
myOffset
=
value
.
selection
.
start
.
offset
-
1
;
const
decorations
=
parentBlock
.
getDecorations
(
editor
as
any
);
const
filteredDecorations
=
decorations
.
filter
(
decoration
=>
decoration
.
start
.
offset
<=
myOffset
&&
decoration
.
end
.
offset
>
myOffset
&&
decoration
.
type
===
TOKEN_MARK
)
.
toArray
();
// Find the first label key to the left of the cursor
const
labelKeyDec
=
decorations
.
filter
(
decoration
=>
{
return
(
decoration
.
end
.
offset
<=
myOffset
&&
decoration
.
type
===
TOKEN_MARK
&&
decoration
.
data
.
get
(
'className'
).
includes
(
'label-key'
)
);
})
.
last
();
const
labelKey
=
labelKeyDec
&&
value
.
focusText
.
text
.
slice
(
labelKeyDec
.
start
.
offset
,
labelKeyDec
.
end
.
offset
);
const
wrapperClasses
=
filteredDecorations
.
map
(
decoration
=>
decoration
.
data
.
get
(
'className'
))
.
join
(
' '
)
.
split
(
' '
)
.
filter
(
className
=>
className
.
length
);
let
text
=
value
.
focusText
.
text
;
let
prefix
=
text
.
slice
(
0
,
selection
.
focus
.
offset
);
if
(
filteredDecorations
.
length
)
{
text
=
value
.
focusText
.
text
.
slice
(
filteredDecorations
[
0
].
start
.
offset
,
filteredDecorations
[
0
].
end
.
offset
);
prefix
=
value
.
focusText
.
text
.
slice
(
filteredDecorations
[
0
].
start
.
offset
,
selection
.
focus
.
offset
);
}
// Label values could have valid characters erased if `cleanText()` is
// blindly applied, which would undesirably interfere with suggestions
const
labelValueMatch
=
prefix
.
match
(
/
(?:
!
?
=~
?
"
?
|"
)(
.*
)
/
);
if
(
labelValueMatch
)
{
prefix
=
labelValueMatch
[
1
];
}
else
if
(
cleanText
)
{
prefix
=
cleanText
(
prefix
);
}
const
{
suggestions
,
context
}
=
await
onTypeahead
({
prefix
,
text
,
value
,
wrapperClasses
,
labelKey
,
});
const
filteredSuggestions
=
suggestions
.
map
(
group
=>
{
if
(
!
group
.
items
)
{
return
group
;
}
const
handleTypeahead
=
async
(
editor
:
CoreEditor
,
onStateChange
:
(
state
:
Partial
<
SuggestionsState
>
)
=>
void
,
onTypeahead
?:
(
typeahead
:
TypeaheadInput
)
=>
Promise
<
TypeaheadOutput
>
,
cleanText
?:
(
text
:
string
)
=>
string
):
Promise
<
void
>
=>
{
if
(
!
onTypeahead
)
{
return
null
;
}
const
{
value
}
=
editor
;
const
{
selection
}
=
value
;
// Get decorations associated with the current line
const
parentBlock
=
value
.
document
.
getClosestBlock
(
value
.
focusBlock
.
key
);
const
myOffset
=
value
.
selection
.
start
.
offset
-
1
;
const
decorations
=
parentBlock
.
getDecorations
(
editor
as
any
);
const
filteredDecorations
=
decorations
.
filter
(
decoration
=>
decoration
.
start
.
offset
<=
myOffset
&&
decoration
.
end
.
offset
>
myOffset
&&
decoration
.
type
===
TOKEN_MARK
)
.
toArray
();
// Find the first label key to the left of the cursor
const
labelKeyDec
=
decorations
.
filter
(
decoration
=>
{
return
(
decoration
.
end
.
offset
<=
myOffset
&&
decoration
.
type
===
TOKEN_MARK
&&
decoration
.
data
.
get
(
'className'
).
includes
(
'label-key'
)
);
})
.
last
();
const
labelKey
=
labelKeyDec
&&
value
.
focusText
.
text
.
slice
(
labelKeyDec
.
start
.
offset
,
labelKeyDec
.
end
.
offset
);
const
wrapperClasses
=
filteredDecorations
.
map
(
decoration
=>
decoration
.
data
.
get
(
'className'
))
.
join
(
' '
)
.
split
(
' '
)
.
filter
(
className
=>
className
.
length
);
let
text
=
value
.
focusText
.
text
;
let
prefix
=
text
.
slice
(
0
,
selection
.
focus
.
offset
);
if
(
filteredDecorations
.
length
)
{
text
=
value
.
focusText
.
text
.
slice
(
filteredDecorations
[
0
].
start
.
offset
,
filteredDecorations
[
0
].
end
.
offset
);
prefix
=
value
.
focusText
.
text
.
slice
(
filteredDecorations
[
0
].
start
.
offset
,
selection
.
focus
.
offset
);
}
// Label values could have valid characters erased if `cleanText()` is
// blindly applied, which would undesirably interfere with suggestions
const
labelValueMatch
=
prefix
.
match
(
/
(?:
!
?
=~
?
"
?
|"
)(
.*
)
/
);
if
(
labelValueMatch
)
{
prefix
=
labelValueMatch
[
1
];
}
else
if
(
cleanText
)
{
prefix
=
cleanText
(
prefix
);
}
const
{
suggestions
,
context
}
=
await
onTypeahead
({
prefix
,
text
,
value
,
wrapperClasses
,
labelKey
,
});
const
filteredSuggestions
=
suggestions
.
map
(
group
=>
{
if
(
!
group
.
items
)
{
return
group
;
}
if
(
prefix
)
{
// Filter groups based on prefix
if
(
!
group
.
skipFilter
)
{
group
.
items
=
group
.
items
.
filter
(
c
=>
(
c
.
filterText
||
c
.
label
).
length
>=
prefix
.
length
);
if
(
group
.
prefixMatch
)
{
group
.
items
=
group
.
items
.
filter
(
c
=>
(
c
.
filterText
||
c
.
label
).
startsWith
(
prefix
));
}
else
{
group
.
items
=
group
.
items
.
filter
(
c
=>
(
c
.
filterText
||
c
.
label
).
includes
(
prefix
));
}
if
(
prefix
)
{
// Filter groups based on prefix
if
(
!
group
.
skipFilter
)
{
group
.
items
=
group
.
items
.
filter
(
c
=>
(
c
.
filterText
||
c
.
label
).
length
>=
prefix
.
length
);
if
(
group
.
prefixMatch
)
{
group
.
items
=
group
.
items
.
filter
(
c
=>
(
c
.
filterText
||
c
.
label
).
startsWith
(
prefix
));
}
else
{
group
.
items
=
group
.
items
.
filter
(
c
=>
(
c
.
filterText
||
c
.
label
).
includes
(
prefix
));
}
// Filter out the already typed value (prefix) unless it inserts custom text
group
.
items
=
group
.
items
.
filter
(
c
=>
c
.
insertText
||
(
c
.
filterText
||
c
.
label
)
!==
prefix
);
}
if
(
!
group
.
skipSort
)
{
group
.
items
=
sortBy
(
group
.
items
,
(
item
:
CompletionItem
)
=>
item
.
sortText
||
item
.
label
);
}
// Filter out the already typed value (prefix) unless it inserts custom text
group
.
items
=
group
.
items
.
filter
(
c
=>
c
.
insertText
||
(
c
.
filterText
||
c
.
label
)
!==
prefix
);
}
return
group
;
})
.
filter
(
group
=>
group
.
items
&&
group
.
items
.
length
);
// Filter out empty groups
if
(
!
group
.
skipSort
)
{
group
.
items
=
sortBy
(
group
.
items
,
(
item
:
CompletionItem
)
=>
item
.
sortText
||
item
.
label
);
}
state
=
{
...
state
,
groupedItems
:
filteredSuggestions
,
typeaheadPrefix
:
prefix
,
typeaheadContext
:
context
,
typeaheadText
:
text
,
};
return
group
;
})
.
filter
(
group
=>
group
.
items
&&
group
.
items
.
length
);
// Filter out empty groups
onStateChange
({
groupedItems
:
filteredSuggestions
,
typeaheadPrefix
:
prefix
,
typeaheadContext
:
context
,
typeaheadText
:
text
,
});
// Bogus edit to force re-render
return
editor
.
blur
().
focus
();
},
TYPEAHEAD_DEBOUNCE
);
// Bogus edit to force re-render
editor
.
blur
().
focus
();
};
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment