2021-06-03 12:47:13 +02:00
import NoteContextAwareWidget from "../note_context_aware_widget.js" ;
2021-07-05 09:44:41 +02:00
import keyboardActionsService from "../../services/keyboard_actions.js" ;
2021-10-26 20:14:56 +02:00
import attributeService from "../../services/attributes.js" ;
2025-01-19 22:46:37 +02:00
import type CommandButtonWidget from "../buttons/command_button.js" ;
import type FNote from "../../entities/fnote.js" ;
import type { NoteType } from "../../entities/fnote.js" ;
import type { EventData , EventNames } from "../../components/app_context.js" ;
2021-06-03 12:47:13 +02:00
const TPL = `
< div class = "ribbon-container" >
< style >
. ribbon - container {
2021-06-13 22:55:31 +02:00
margin - bottom : 5px ;
2021-06-03 12:47:13 +02:00
}
2025-01-19 22:46:37 +02:00
2021-06-03 12:47:13 +02:00
. ribbon - top - row {
display : flex ;
}
2025-01-19 22:46:37 +02:00
2021-06-03 12:47:13 +02:00
. ribbon - tab - container {
display : flex ;
flex - direction : row ;
justify - content : center ;
margin - left : 10px ;
flex - grow : 1 ;
flex - flow : row wrap ;
}
2025-01-19 22:46:37 +02:00
2021-06-03 12:47:13 +02:00
. ribbon - tab - title {
color : var ( -- muted - text - color ) ;
2025-01-19 22:46:37 +02:00
border - bottom : 1px solid var ( -- main - border - color ) ;
2021-06-05 22:40:17 +02:00
min - width : 24px ;
flex - basis : 24px ;
2021-10-04 21:53:57 +02:00
max - width : max - content ;
2021-06-05 22:40:17 +02:00
flex - grow : 10 ;
2021-06-03 12:47:13 +02:00
}
. ribbon - tab - title . bx {
font - size : 150 % ;
position : relative ;
top : 3px ;
}
2025-01-19 22:46:37 +02:00
2021-06-03 12:47:13 +02:00
. ribbon - tab - title . active {
color : var ( -- main - text - color ) ;
2023-05-09 16:29:04 -04:00
border - bottom : 3px solid var ( -- main - text - color ) ;
2021-06-03 12:47:13 +02:00
white - space : nowrap ;
overflow : hidden ;
text - overflow : ellipsis ;
}
2025-01-19 22:46:37 +02:00
2021-06-03 12:47:13 +02:00
. ribbon - tab - title :hover {
cursor : pointer ;
}
. ribbon - tab - title :hover {
color : var ( -- main - text - color ) ;
}
2025-01-19 22:46:37 +02:00
2021-06-03 12:47:13 +02:00
. ribbon - tab - title :first - of - type {
padding - left : 10px ;
}
2025-01-19 22:46:37 +02:00
2021-06-03 12:47:13 +02:00
. ribbon - tab - spacer {
2021-06-05 22:40:17 +02:00
flex - basis : 0 ;
min - width : 0 ;
max - width : 35px ;
flex - grow : 1 ;
2021-06-03 12:47:13 +02:00
border - bottom : 1px solid var ( -- main - border - color ) ;
}
2025-01-19 22:46:37 +02:00
2021-06-03 12:47:13 +02:00
. ribbon - tab - spacer :last - of - type {
flex - grow : 1 ;
2021-06-05 22:40:17 +02:00
flex - basis : 0 ;
min - width : 0 ;
max - width : 10000px ;
2021-06-03 12:47:13 +02:00
}
2025-01-19 22:46:37 +02:00
2021-06-03 12:47:13 +02:00
. ribbon - button - container {
display : flex ;
2025-01-19 22:46:37 +02:00
border - bottom : 1px solid var ( -- main - border - color ) ;
2021-06-03 12:47:13 +02:00
margin - right : 5px ;
}
2025-01-19 22:46:37 +02:00
2021-06-13 22:55:31 +02:00
. ribbon - button - container > * {
2021-06-03 12:47:13 +02:00
position : relative ;
top : - 3 px ;
margin - left : 10px ;
}
2025-01-19 22:46:37 +02:00
2021-06-03 12:47:13 +02:00
. ribbon - body {
display : none ;
border - bottom : 1px solid var ( -- main - border - color ) ;
margin - left : 10px ;
2023-10-19 23:54:36 +02:00
margin - right : 5px ; /* needs to have this value so that the bottom border is the same width as the top one */
2021-06-03 12:47:13 +02:00
}
2025-01-19 22:46:37 +02:00
2021-06-03 12:47:13 +02:00
. ribbon - body . active {
display : block ;
}
2025-01-19 22:46:37 +02:00
2021-06-03 12:47:13 +02:00
. ribbon - tab - title - label {
display : none ;
}
2025-01-19 22:46:37 +02:00
2021-06-03 12:47:13 +02:00
. ribbon - tab - title . active . ribbon - tab - title - label {
display : inline ;
}
< / style >
< div class = "ribbon-top-row" >
< div class = "ribbon-tab-container" > < / div >
< div class = "ribbon-button-container" > < / div >
< / div >
2025-01-19 22:46:37 +02:00
2021-06-03 12:47:13 +02:00
< div class = "ribbon-body-container" > < / div >
< / div > ` ;
export default class RibbonContainer extends NoteContextAwareWidget {
2025-01-19 22:46:37 +02:00
private lastActiveComponentId? : string | null ;
private lastNoteType? : NoteType ;
private ribbonWidgets : NoteContextAwareWidget [ ] ;
private buttonWidgets : CommandButtonWidget [ ] ;
private $tabContainer ! : JQuery < HTMLElement > ;
private $buttonContainer ! : JQuery < HTMLElement > ;
private $bodyContainer ! : JQuery < HTMLElement > ;
2021-06-03 12:47:13 +02:00
constructor ( ) {
super ( ) ;
2021-06-13 22:55:31 +02:00
this . contentSized ( ) ;
2021-06-03 12:47:13 +02:00
this . ribbonWidgets = [ ] ;
this . buttonWidgets = [ ] ;
}
2023-02-15 15:57:33 +01:00
isEnabled() {
2025-01-19 22:46:37 +02:00
return super . isEnabled ( ) && this . noteContext ? . viewScope ? . viewMode === "default" ;
2023-02-15 15:57:33 +01:00
}
2025-01-19 22:46:37 +02:00
ribbon ( widget : NoteContextAwareWidget ) { // TODO: Base class
2021-06-03 12:47:13 +02:00
super . child ( widget ) ;
this . ribbonWidgets . push ( widget ) ;
return this ;
}
2025-01-19 22:46:37 +02:00
button ( widget : CommandButtonWidget ) {
2021-06-03 12:47:13 +02:00
super . child ( widget ) ;
this . buttonWidgets . push ( widget ) ;
return this ;
}
doRender() {
this . $widget = $ ( TPL ) ;
2025-01-09 18:07:02 +02:00
this . $tabContainer = this . $widget . find ( ".ribbon-tab-container" ) ;
this . $buttonContainer = this . $widget . find ( ".ribbon-button-container" ) ;
this . $bodyContainer = this . $widget . find ( ".ribbon-body-container" ) ;
2021-06-03 12:47:13 +02:00
for ( const ribbonWidget of this . ribbonWidgets ) {
2025-01-09 18:07:02 +02:00
this . $bodyContainer . append ( $ ( '<div class="ribbon-body">' ) . attr ( "data-ribbon-component-id" , ribbonWidget . componentId ) . append ( ribbonWidget . render ( ) ) ) ;
2021-06-03 12:47:13 +02:00
}
for ( const buttonWidget of this . buttonWidgets ) {
this . $buttonContainer . append ( buttonWidget . render ( ) ) ;
}
2025-01-09 18:07:02 +02:00
this . $tabContainer . on ( "click" , ".ribbon-tab-title" , ( e ) = > {
const $ribbonTitle = $ ( e . target ) . closest ( ".ribbon-tab-title" ) ;
2021-06-03 12:47:13 +02:00
2021-06-27 12:53:05 +02:00
this . toggleRibbonTab ( $ribbonTitle ) ;
} ) ;
}
2021-06-03 12:47:13 +02:00
2025-01-19 22:46:37 +02:00
toggleRibbonTab ( $ribbonTitle : JQuery < HTMLElement > , refreshActiveTab = true ) {
2021-06-27 12:53:05 +02:00
const activate = ! $ribbonTitle . hasClass ( "active" ) ;
2021-06-03 12:47:13 +02:00
2025-01-09 18:07:02 +02:00
this . $tabContainer . find ( ".ribbon-tab-title" ) . removeClass ( "active" ) ;
this . $bodyContainer . find ( ".ribbon-body" ) . removeClass ( "active" ) ;
2021-06-03 12:47:13 +02:00
2021-06-27 12:53:05 +02:00
if ( activate ) {
2025-01-09 18:07:02 +02:00
const ribbonComponendId = $ribbonTitle . attr ( "data-ribbon-component-id" ) ;
2021-06-03 12:47:13 +02:00
2021-11-04 22:21:49 +01:00
const wasAlreadyActive = this . lastActiveComponentId === ribbonComponendId ;
2021-06-27 12:53:05 +02:00
this . lastActiveComponentId = ribbonComponendId ;
2021-06-03 12:47:13 +02:00
2021-06-27 12:53:05 +02:00
this . $tabContainer . find ( ` .ribbon-tab-title[data-ribbon-component-id=" ${ ribbonComponendId } "] ` ) . addClass ( "active" ) ;
this . $bodyContainer . find ( ` .ribbon-body[data-ribbon-component-id=" ${ ribbonComponendId } "] ` ) . addClass ( "active" ) ;
2021-06-03 12:47:13 +02:00
2021-06-27 12:53:05 +02:00
const activeChild = this . getActiveRibbonWidget ( ) ;
2025-01-19 22:46:37 +02:00
if ( activeChild && ( refreshActiveTab || ! wasAlreadyActive ) && this . noteContext && this . notePath ) {
2025-01-09 18:07:02 +02:00
const handleEventPromise = activeChild . handleEvent ( "noteSwitched" , { noteContext : this.noteContext , notePath : this.notePath } ) ;
2022-06-23 23:03:35 +02:00
2022-07-30 23:43:20 +02:00
if ( refreshActiveTab ) {
if ( handleEventPromise ) {
2025-01-19 22:46:37 +02:00
handleEventPromise . then ( ( ) = > ( activeChild as any ) . focus ( ) ) ; // TODO: Base class
2022-07-30 23:43:20 +02:00
} else {
2025-01-19 22:46:37 +02:00
// TODO: Base class
( activeChild as any ) ? . focus ( ) ;
2022-07-30 23:43:20 +02:00
}
2022-06-23 23:03:35 +02:00
}
2021-06-03 12:47:13 +02:00
}
2021-06-27 12:53:05 +02:00
} else {
this . lastActiveComponentId = null ;
}
2021-06-03 12:47:13 +02:00
}
2022-06-08 23:44:43 +02:00
async noteSwitched() {
this . lastActiveComponentId = null ;
await super . noteSwitched ( ) ;
}
2025-01-19 22:46:37 +02:00
async refreshWithNote ( note : FNote , noExplicitActivation = false ) {
2021-06-03 12:47:13 +02:00
this . lastNoteType = note . type ;
let $ribbonTabToActivate , $lastActiveRibbon ;
this . $tabContainer . empty ( ) ;
for ( const ribbonWidget of this . ribbonWidgets ) {
2025-01-19 22:46:37 +02:00
// TODO: Base class for ribbon widget
const ret = await ( ribbonWidget as any ) . getTitle ( note ) ;
2021-06-03 12:47:13 +02:00
if ( ! ret . show ) {
continue ;
}
const $ribbonTitle = $ ( '<div class="ribbon-tab-title">' )
2025-01-09 18:07:02 +02:00
. attr ( "data-ribbon-component-id" , ribbonWidget . componentId )
2025-01-19 22:46:37 +02:00
. attr ( "data-ribbon-component-name" , ( ribbonWidget as any ) . name as string ) // TODO: base class for ribbon widgets
. append ( $ ( '<span class="ribbon-tab-title-icon">' ) . addClass ( ret . icon ) . attr ( "title" , ret . title ) . attr ( "data-toggle-command" , ( ribbonWidget as any ) . toggleCommand ) ) // TODO: base class
2021-06-03 12:47:13 +02:00
. append ( " " )
. append ( $ ( '<span class="ribbon-tab-title-label">' ) . text ( ret . title ) ) ;
this . $tabContainer . append ( $ribbonTitle ) ;
this . $tabContainer . append ( '<div class="ribbon-tab-spacer">' ) ;
if ( ret . activate && ! this . lastActiveComponentId && ! $ribbonTabToActivate && ! noExplicitActivation ) {
$ribbonTabToActivate = $ribbonTitle ;
}
if ( this . lastActiveComponentId === ribbonWidget . componentId ) {
$lastActiveRibbon = $ribbonTitle ;
}
}
2025-01-09 18:07:02 +02:00
keyboardActionsService . getActions ( ) . then ( ( actions ) = > {
this . $tabContainer . find ( ".ribbon-tab-title-icon" ) . tooltip ( {
2025-01-19 22:46:37 +02:00
title : ( ) = > {
2021-07-05 09:44:41 +02:00
const toggleCommandName = $ ( this ) . attr ( "data-toggle-command" ) ;
2025-01-09 18:07:02 +02:00
const action = actions . find ( ( act ) = > act . actionName === toggleCommandName ) ;
2021-07-05 09:44:41 +02:00
const title = $ ( this ) . attr ( "data-title" ) ;
if ( action && action . effectiveShortcuts . length > 0 ) {
return ` ${ title } ( ${ action . effectiveShortcuts . join ( ", " ) } ) ` ;
2025-01-09 18:07:02 +02:00
} else {
2025-01-19 22:46:37 +02:00
return title ? ? "" ;
2021-07-05 09:44:41 +02:00
}
}
} ) ;
} ) ;
2021-06-03 12:47:13 +02:00
if ( ! $ribbonTabToActivate ) {
$ribbonTabToActivate = $lastActiveRibbon ;
}
if ( $ribbonTabToActivate ) {
2021-10-30 15:22:20 +02:00
this . toggleRibbonTab ( $ribbonTabToActivate , false ) ;
2025-01-09 18:07:02 +02:00
} else {
this . $bodyContainer . find ( ".ribbon-body" ) . removeClass ( "active" ) ;
2021-06-03 12:47:13 +02:00
}
}
2025-01-19 22:46:37 +02:00
isRibbonTabActive ( name : string ) {
2021-06-27 12:53:05 +02:00
const $ribbonComponent = this . $widget . find ( ` .ribbon-tab-title[data-ribbon-component-name=' ${ name } '] ` ) ;
return $ribbonComponent . hasClass ( "active" ) ;
}
2025-01-19 22:46:37 +02:00
ensureOwnedAttributesAreOpen ( ntxId : string | null | undefined ) {
if ( ntxId && this . isNoteContext ( ntxId ) && ! this . isRibbonTabActive ( "ownedAttributes" ) ) {
2025-01-09 18:07:02 +02:00
this . toggleRibbonTabWithName ( "ownedAttributes" , ntxId ) ;
2021-06-27 12:53:05 +02:00
}
}
2025-01-19 22:46:37 +02:00
addNewLabelEvent ( { ntxId } : EventData < "addNewLabel" > ) {
2021-06-27 12:53:05 +02:00
this . ensureOwnedAttributesAreOpen ( ntxId ) ;
}
2025-01-19 22:46:37 +02:00
addNewRelationEvent ( { ntxId } : EventData < "addNewRelation" > ) {
2021-06-27 12:53:05 +02:00
this . ensureOwnedAttributesAreOpen ( ntxId ) ;
}
2025-01-19 22:46:37 +02:00
toggleRibbonTabWithName ( name : string , ntxId? : string ) {
2021-06-27 12:53:05 +02:00
if ( ! this . isNoteContext ( ntxId ) ) {
return false ;
}
const $ribbonComponent = this . $widget . find ( ` .ribbon-tab-title[data-ribbon-component-name=' ${ name } '] ` ) ;
if ( $ribbonComponent ) {
this . toggleRibbonTab ( $ribbonComponent ) ;
}
}
2025-01-19 22:46:37 +02:00
handleEvent < T extends EventNames > ( name : T , data : EventData < T > ) {
2021-06-27 12:53:05 +02:00
const PREFIX = "toggleRibbonTab" ;
if ( name . startsWith ( PREFIX ) ) {
let componentName = name . substr ( PREFIX . length ) ;
componentName = componentName [ 0 ] . toLowerCase ( ) + componentName . substr ( 1 ) ;
2025-01-19 22:46:37 +02:00
this . toggleRibbonTabWithName ( componentName , ( data as any ) . ntxId ) ;
2025-01-09 18:07:02 +02:00
} else {
2021-06-27 12:53:05 +02:00
return super . handleEvent ( name , data ) ;
}
}
2025-01-19 22:46:37 +02:00
async handleEventInChildren < T extends EventNames > ( name : T , data : EventData < T > ) {
2025-01-09 18:07:02 +02:00
if ( [ "activeContextChanged" , "setNoteContext" ] . includes ( name ) ) {
2021-06-03 12:47:13 +02:00
// won't trigger .refresh();
2025-01-19 22:46:37 +02:00
await super . handleEventInChildren ( "setNoteContext" , data as EventData < "activeContextChanged" | "setNoteContext" > ) ;
2025-01-09 18:07:02 +02:00
} else if ( this . isEnabled ( ) || name === "initialRenderComplete" ) {
2021-06-03 12:47:13 +02:00
const activeRibbonWidget = this . getActiveRibbonWidget ( ) ;
// forward events only to active ribbon tab, inactive ones don't need to be updated
if ( activeRibbonWidget ) {
await activeRibbonWidget . handleEvent ( name , data ) ;
}
for ( const buttonWidget of this . buttonWidgets ) {
await buttonWidget . handleEvent ( name , data ) ;
}
}
}
2025-01-19 22:46:37 +02:00
entitiesReloadedEvent ( { loadResults } : EventData < "entitiesReloaded" > ) {
if ( ! this . note ) {
return ;
}
if ( this . noteId && loadResults . isNoteReloaded ( this . noteId ) && this . lastNoteType !== this . note . type ) {
2021-06-03 12:47:13 +02:00
// note type influences the list of available ribbon tabs the most
2023-06-30 11:18:34 +02:00
// check for the type is so that we don't update on each title rename
2021-06-03 12:47:13 +02:00
this . lastNoteType = this . note . type ;
this . refresh ( ) ;
2025-01-09 18:07:02 +02:00
} else if ( loadResults . getAttributeRows ( this . componentId ) . find ( ( attr ) = > attributeService . isAffecting ( attr , this . note ) ) ) {
2021-10-26 20:14:56 +02:00
this . refreshWithNote ( this . note , true ) ;
}
2021-06-03 12:47:13 +02:00
}
2025-01-19 22:46:37 +02:00
async noteTypeMimeChangedEvent() {
2024-11-18 20:52:35 +02:00
// We are ignoring the event which triggers a refresh since it is usually already done by a different
// event and causing a race condition in which the items appear twice.
}
2024-11-09 10:33:45 +02:00
/ * *
* Executed as soon as the user presses the "Edit" floating button in a read - only text note .
2025-01-09 18:07:02 +02:00
*
2024-11-09 10:33:45 +02:00
* < p >
* We need to refresh the ribbon for cases such as the classic editor which relies on the read - only state .
* /
2025-01-09 18:07:02 +02:00
readOnlyTemporarilyDisabledEvent() {
2024-11-09 10:33:45 +02:00
this . refresh ( ) ;
}
2021-06-03 12:47:13 +02:00
getActiveRibbonWidget() {
2025-01-09 18:07:02 +02:00
return this . ribbonWidgets . find ( ( ch ) = > ch . componentId === this . lastActiveComponentId ) ;
2021-06-03 12:47:13 +02:00
}
}