1
0
mirror of https://github.com/mattermost/focalboard.git synced 2025-07-15 23:54:29 +02:00

Ran npm run fix

This commit is contained in:
Chen-I Lim
2020-10-20 12:52:56 -07:00
parent 36a104f45b
commit 9255bd4ded
51 changed files with 407 additions and 406 deletions

View File

@ -69,6 +69,7 @@
"rules": { "rules": {
"import/no-unresolved": 0, // ts handles this better "import/no-unresolved": 0, // ts handles this better
"camelcase": 0, "camelcase": 0,
"semi": "off",
"@typescript-eslint/naming-convention": [ "@typescript-eslint/naming-convention": [
2, 2,
{ {

View File

@ -1,16 +1,16 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
import { import {
BrowserRouter as Router, BrowserRouter as Router,
Switch, Switch,
Route, Route,
Link, Link,
} from 'react-router-dom'; } from 'react-router-dom'
import LoginPage from './pages/loginPage'; import LoginPage from './pages/loginPage'
import BoardPage from './pages/boardPage'; import BoardPage from './pages/boardPage'
export default function App() { export default function App() {
return ( return (

View File

@ -1,9 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {BoardTree} from './boardTree'; import {BoardTree} from './boardTree'
import mutator from './mutator'; import mutator from './mutator'
import {IBlock} from './octoTypes'; import {IBlock} from './octoTypes'
import {Utils} from './utils'; import {Utils} from './utils'
interface Archive { interface Archive {
version: number version: number
@ -40,7 +40,7 @@ class Archiver {
const date = new Date() const date = new Date()
const filename = `archive-${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}.octo` const filename = `archive-${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}.octo`
const link = document.createElement('a') const link = document.createElement('a')
link.style.display = 'none'; link.style.display = 'none'
// const file = new Blob([content], { type: "text/json" }) // const file = new Blob([content], { type: "text/json" })
// link.href = URL.createObjectURL(file) // link.href = URL.createObjectURL(file)
@ -55,8 +55,8 @@ class Archiver {
static importFullArchive(onComplete?: () => void): void { static importFullArchive(onComplete?: () => void): void {
const input = document.createElement('input') const input = document.createElement('input')
input.type = 'file'; input.type = 'file'
input.accept = '.octo'; input.accept = '.octo'
input.onchange = async () => { input.onchange = async () => {
const file = input.files[0] const file = input.files[0]
const contents = await (new Response(file)).text() const contents = await (new Response(file)).text()
@ -72,16 +72,16 @@ class Archiver {
return false return false
} }
return true return true
}); })
Utils.log(`Import ${filteredBlocks.length} filtered blocks.`) Utils.log(`Import ${filteredBlocks.length} filtered blocks.`)
await mutator.importFullArchive(filteredBlocks) await mutator.importFullArchive(filteredBlocks)
Utils.log('Import completed') Utils.log('Import completed')
onComplete?.() onComplete?.()
}; }
input.style.display = 'none'; input.style.display = 'none'
document.body.appendChild(input) document.body.appendChild(input)
input.click() input.click()

View File

@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {randomEmojiList} from './emojiList'; import {randomEmojiList} from './emojiList'
class BlockIcons { class BlockIcons {
static readonly shared = new BlockIcons() static readonly shared = new BlockIcons()

View File

@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {IBlock} from '../octoTypes'; import {IBlock} from '../octoTypes'
import {Utils} from '../utils'; import {Utils} from '../utils'
class Block implements IBlock { class Block implements IBlock {
id: string = Utils.createGuid() id: string = Utils.createGuid()

View File

@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {Block} from './block'; import {Block} from './block'
type PropertyType = 'text' | 'number' | 'select' | 'multiSelect' | 'date' | 'person' | 'file' | 'checkbox' | 'url' | 'email' | 'phone' | 'createdTime' | 'createdBy' | 'updatedTime' | 'updatedBy' type PropertyType = 'text' | 'number' | 'select' | 'multiSelect' | 'date' | 'person' | 'file' | 'checkbox' | 'url' | 'email' | 'phone' | 'createdTime' | 'createdBy' | 'updatedTime' | 'updatedBy'
@ -34,7 +34,7 @@ class Board extends Block {
constructor(block: any = {}) { constructor(block: any = {}) {
super(block) super(block)
this.type = 'board'; this.type = 'board'
if (block.fields?.cardProperties) { if (block.fields?.cardProperties) {
// Deep clone of card properties and their options // Deep clone of card properties and their options
@ -45,7 +45,7 @@ class Board extends Block {
type: o.type, type: o.type,
options: o.options ? o.options.map((option) => ({...option})) : [], options: o.options ? o.options.map((option) => ({...option})) : [],
} }
}); })
} else { } else {
this.cardProperties = [] this.cardProperties = []
} }

View File

@ -1,8 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {FilterGroup} from '../filterGroup'; import {FilterGroup} from '../filterGroup'
import {Block} from './block'; import {Block} from './block'
type IViewType = 'board' | 'table' | 'calendar' | 'list' | 'gallery' type IViewType = 'board' | 'table' | 'calendar' | 'list' | 'gallery'
type ISortOption = { propertyId: '__name' | string, reversed: boolean } type ISortOption = { propertyId: '__name' | string, reversed: boolean }
@ -46,7 +46,7 @@ class BoardView extends Block {
constructor(block: any = {}) { constructor(block: any = {}) {
super(block) super(block)
this.type = 'view'; this.type = 'view'
this.sortOptions = block.fields?.sortOptions?.map((o: ISortOption) => ({...o})) || [] // Deep clone this.sortOptions = block.fields?.sortOptions?.map((o: ISortOption) => ({...o})) || [] // Deep clone
this.visiblePropertyIds = block.fields?.visiblePropertyIds?.slice() || [] this.visiblePropertyIds = block.fields?.visiblePropertyIds?.slice() || []
@ -54,7 +54,7 @@ class BoardView extends Block {
// TODO: Remove this fixup code // TODO: Remove this fixup code
if (block.schema !== 1) { if (block.schema !== 1) {
this.viewType = block.viewType || 'board'; this.viewType = block.viewType || 'board'
this.groupById = block.groupById this.groupById = block.groupById
this.sortOptions = block.sortOptions ? block.sortOptions.map((o: ISortOption) => ({...o})) : [] // Deep clone this.sortOptions = block.sortOptions ? block.sortOptions.map((o: ISortOption) => ({...o})) : [] // Deep clone
this.visiblePropertyIds = block.visiblePropertyIds ? block.visiblePropertyIds.slice() : [] this.visiblePropertyIds = block.visiblePropertyIds ? block.visiblePropertyIds.slice() : []
@ -62,7 +62,7 @@ class BoardView extends Block {
} }
if (!this.viewType) { if (!this.viewType) {
this.viewType = 'board'; this.viewType = 'board'
} }
} }
} }

View File

@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {Block} from './block'; import {Block} from './block'
class Card extends Block { class Card extends Block {
get icon(): string { get icon(): string {
@ -19,7 +19,7 @@ class Card extends Block {
constructor(block: any = {}) { constructor(block: any = {}) {
super(block) super(block)
this.type = 'card'; this.type = 'card'
this.properties = {...(block.fields?.properties || {})} this.properties = {...(block.fields?.properties || {})}
} }

View File

@ -1,11 +1,11 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {Block} from './block'; import {Block} from './block'
class CommentBlock extends Block { class CommentBlock extends Block {
constructor(block: any = {}) { constructor(block: any = {}) {
super(block) super(block)
this.type = 'comment'; this.type = 'comment'
} }
} }

View File

@ -1,8 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {IOrderedBlock} from '../octoTypes'; import {IOrderedBlock} from '../octoTypes'
import {Block} from './block'; import {Block} from './block'
class ImageBlock extends Block implements IOrderedBlock { class ImageBlock extends Block implements IOrderedBlock {
get order(): number { get order(): number {
@ -21,7 +21,7 @@ class ImageBlock extends Block implements IOrderedBlock {
constructor(block: any = {}) { constructor(block: any = {}) {
super(block) super(block)
this.type = 'image'; this.type = 'image'
} }
} }

View File

@ -1,8 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {IOrderedBlock} from '../octoTypes'; import {IOrderedBlock} from '../octoTypes'
import {Block} from './block'; import {Block} from './block'
class TextBlock extends Block implements IOrderedBlock { class TextBlock extends Block implements IOrderedBlock {
get order(): number { get order(): number {
@ -14,7 +14,7 @@ class TextBlock extends Block implements IOrderedBlock {
constructor(block: any = {}) { constructor(block: any = {}) {
super(block) super(block)
this.type = 'text'; this.type = 'text'
} }
} }

View File

@ -1,14 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {Block} from './blocks/block'; import {Block} from './blocks/block'
import {Board, IPropertyOption, IPropertyTemplate} from './blocks/board'; import {Board, IPropertyOption, IPropertyTemplate} from './blocks/board'
import {BoardView} from './blocks/boardView'; import {BoardView} from './blocks/boardView'
import {Card} from './blocks/card'; import {Card} from './blocks/card'
import {CardFilter} from './cardFilter'; import {CardFilter} from './cardFilter'
import octoClient from './octoClient'; import octoClient from './octoClient'
import {IBlock} from './octoTypes'; import {IBlock} from './octoTypes'
import {OctoUtils} from './octoUtils'; import {OctoUtils} from './octoUtils'
import {Utils} from './utils'; import {Utils} from './utils'
type Group = { option: IPropertyOption, cards: Card[] } type Group = { option: IPropertyOption, cards: Card[] }
@ -154,7 +154,7 @@ class BoardTree {
this.emptyGroupCards = this.cards.filter((o) => { this.emptyGroupCards = this.cards.filter((o) => {
const propertyValue = o.properties[groupByPropertyId] const propertyValue = o.properties[groupByPropertyId]
return !propertyValue || !this.groupByProperty.options.find((option) => option.value === propertyValue) return !propertyValue || !this.groupByProperty.options.find((option) => option.value === propertyValue)
}); })
const propertyOptions = this.groupByProperty.options || [] const propertyOptions = this.groupByProperty.options || []
for (const option of propertyOptions) { for (const option of propertyOptions) {
@ -162,7 +162,7 @@ class BoardTree {
filter((o) => { filter((o) => {
const propertyValue = o.properties[groupByPropertyId] const propertyValue = o.properties[groupByPropertyId]
return propertyValue && propertyValue === option.value return propertyValue && propertyValue === option.value
}); })
const group: Group = { const group: Group = {
option, option,
@ -194,8 +194,8 @@ class BoardTree {
if (sortOptions.length < 1) { if (sortOptions.length < 1) {
Utils.log('Default sort') Utils.log('Default sort')
sortedCards = cards.sort((a, b) => { sortedCards = cards.sort((a, b) => {
const aValue = a.title || ''; const aValue = a.title || ''
const bValue = b.title || ''; const bValue = b.title || ''
// Always put empty values at the bottom // Always put empty values at the bottom
if (aValue && !bValue) { if (aValue && !bValue) {
@ -209,14 +209,14 @@ class BoardTree {
} }
return a.createAt - b.createAt return a.createAt - b.createAt
}); })
} else { } else {
sortOptions.forEach((sortOption) => { sortOptions.forEach((sortOption) => {
if (sortOption.propertyId === '__name') { if (sortOption.propertyId === '__name') {
Utils.log('Sort by name') Utils.log('Sort by name')
sortedCards = cards.sort((a, b) => { sortedCards = cards.sort((a, b) => {
const aValue = a.title || ''; const aValue = a.title || ''
const bValue = b.title || ''; const bValue = b.title || ''
// Always put empty values at the bottom, newest last // Always put empty values at the bottom, newest last
if (aValue && !bValue) { if (aValue && !bValue) {
@ -234,7 +234,7 @@ class BoardTree {
result = -result result = -result
} }
return result return result
}); })
} else { } else {
const sortPropertyId = sortOption.propertyId const sortPropertyId = sortOption.propertyId
const template = board.cardProperties.find((o) => o.id === sortPropertyId) const template = board.cardProperties.find((o) => o.id === sortPropertyId)
@ -255,8 +255,8 @@ class BoardTree {
return a.createAt - b.createAt return a.createAt - b.createAt
} }
const aValue = a.properties[sortPropertyId] || ''; const aValue = a.properties[sortPropertyId] || ''
const bValue = b.properties[sortPropertyId] || ''; const bValue = b.properties[sortPropertyId] || ''
let result = 0 let result = 0
if (template.type === 'select') { if (template.type === 'select') {
// Always put empty values at the bottom // Always put empty values at the bottom
@ -313,7 +313,7 @@ class BoardTree {
result = -result result = -result
} }
return result return result
}); })
} }
}) })
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {IPropertyTemplate} from './blocks/board'; import {IPropertyTemplate} from './blocks/board'
import {Card} from './blocks/card'; import {Card} from './blocks/card'
import {FilterClause} from './filterClause'; import {FilterClause} from './filterClause'
import {FilterGroup} from './filterGroup'; import {FilterGroup} from './filterGroup'
import {Utils} from './utils'; import {Utils} from './utils'
class CardFilter { class CardFilter {
static applyFilterGroup(filterGroup: FilterGroup, templates: IPropertyTemplate[], cards: Card[]): Card[] { static applyFilterGroup(filterGroup: FilterGroup, templates: IPropertyTemplate[], cards: Card[]): Card[] {
@ -89,7 +89,7 @@ class CardFilter {
filters.forEach((filterClause) => { filters.forEach((filterClause) => {
const p = this.propertyThatMeetsFilterClause(filterClause as FilterClause, templates) const p = this.propertyThatMeetsFilterClause(filterClause as FilterClause, templates)
result[p.id] = p.value result[p.id] = p.value
}); })
return result return result
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {Block} from './blocks/block'; import {Block} from './blocks/block'
import {Card} from './blocks/card'; import {Card} from './blocks/card'
import octoClient from './octoClient'; import octoClient from './octoClient'
import {IBlock, IOrderedBlock} from './octoTypes'; import {IBlock, IOrderedBlock} from './octoTypes'
import {OctoUtils} from './octoUtils'; import {OctoUtils} from './octoUtils'
class CardTree { class CardTree {
card: Card card: Card

View File

@ -1,14 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
import {Block} from '../blocks/block'; import {Block} from '../blocks/block'
import {IPropertyTemplate} from '../blocks/board'; import {IPropertyTemplate} from '../blocks/board'
import {Card} from '../blocks/card'; import {Card} from '../blocks/card'
import {Menu} from '../menu'; import {Menu} from '../menu'
import mutator from '../mutator'; import mutator from '../mutator'
import {OctoUtils} from '../octoUtils'; import {OctoUtils} from '../octoUtils'
import {Utils} from '../utils'; import {Utils} from '../utils'
type BoardCardProps = { type BoardCardProps = {
card: Card card: Card
@ -49,7 +49,7 @@ class BoardCard extends React.Component<BoardCardProps, BoardCardState> {
optionsButtonRef.current.style.display = null optionsButtonRef.current.style.display = null
}} }}
onMouseLeave={() => { onMouseLeave={() => {
optionsButtonRef.current.style.display = 'none'; optionsButtonRef.current.style.display = 'none'
}} }}
> >
<div <div
@ -86,12 +86,12 @@ class BoardCard extends React.Component<BoardCardProps, BoardCardState> {
switch (id) { switch (id) {
case 'delete': { case 'delete': {
mutator.deleteBlock(card, 'delete card') mutator.deleteBlock(card, 'delete card')
break; break
} }
case 'duplicate': { case 'duplicate': {
const newCard = Block.duplicate(card) const newCard = Block.duplicate(card)
mutator.insertBlock(newCard, 'duplicate card') mutator.insertBlock(newCard, 'duplicate card')
break; break
} }
default: { default: {
Utils.assertFailure(`Unhandled menu id: ${id}`) Utils.assertFailure(`Unhandled menu id: ${id}`)

View File

@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
type Props = { type Props = {
onDrop?: (e: React.DragEvent<HTMLDivElement>) => void onDrop?: (e: React.DragEvent<HTMLDivElement>) => void

View File

@ -1,28 +1,28 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
import {Archiver} from '../archiver'; import {Archiver} from '../archiver'
import {BlockIcons} from '../blockIcons'; import {BlockIcons} from '../blockIcons'
import {IPropertyOption} from '../blocks/board'; import {IPropertyOption} from '../blocks/board'
import {Card} from '../blocks/card'; import {Card} from '../blocks/card'
import {BoardTree} from '../boardTree'; import {BoardTree} from '../boardTree'
import {CardFilter} from '../cardFilter'; import {CardFilter} from '../cardFilter'
import ViewMenu from '../components/viewMenu'; import ViewMenu from '../components/viewMenu'
import {Constants} from '../constants'; import {Constants} from '../constants'
import {Menu as OldMenu} from '../menu'; import {Menu as OldMenu} from '../menu'
import mutator from '../mutator'; import mutator from '../mutator'
import {OctoUtils} from '../octoUtils'; import {OctoUtils} from '../octoUtils'
import {Utils} from '../utils'; import {Utils} from '../utils'
import Menu from '../widgets/menu'; import Menu from '../widgets/menu'
import MenuWrapper from '../widgets/menuWrapper'; import MenuWrapper from '../widgets/menuWrapper'
import {BoardCard} from './boardCard'; import {BoardCard} from './boardCard'
import {BoardColumn} from './boardColumn'; import {BoardColumn} from './boardColumn'
import Button from './button'; import Button from './button'
import {CardDialog} from './cardDialog'; import {CardDialog} from './cardDialog'
import {Editable} from './editable'; import {Editable} from './editable'
import RootPortal from './rootPortal'; import RootPortal from './rootPortal'
type Props = { type Props = {
boardTree?: BoardTree boardTree?: BoardTree
@ -404,7 +404,7 @@ class BoardComponent extends React.Component<Props, State> {
this.setState({shownCard: card}) this.setState({shownCard: card})
}, async () => { }, async () => {
this.setState({shownCard: undefined}) this.setState({shownCard: undefined})
}); })
} }
async propertyNameChanged(option: IPropertyOption, text: string) { async propertyNameChanged(option: IPropertyOption, text: string) {
@ -431,19 +431,19 @@ class BoardComponent extends React.Component<Props, State> {
switch (id) { switch (id) {
case 'exportBoardArchive': { case 'exportBoardArchive': {
Archiver.exportBoardTree(boardTree) Archiver.exportBoardTree(boardTree)
break; break
} }
case 'testAdd100Cards': { case 'testAdd100Cards': {
this.testAddCards(100) this.testAddCards(100)
break; break
} }
case 'testAdd1000Cards': { case 'testAdd1000Cards': {
this.testAddCards(1000) this.testAddCards(1000)
break; break
} }
case 'testRandomizeIcons': { case 'testRandomizeIcons': {
this.testRandomizeIcons() this.testRandomizeIcons()
break; break
} }
} }
} }
@ -489,7 +489,7 @@ class BoardComponent extends React.Component<Props, State> {
OldMenu.shared.options = selectProperties.map((o) => { OldMenu.shared.options = selectProperties.map((o) => {
const isVisible = activeView.visiblePropertyIds.includes(o.id) const isVisible = activeView.visiblePropertyIds.includes(o.id)
return {id: o.id, name: o.name, type: 'switch', isOn: isVisible} return {id: o.id, name: o.name, type: 'switch', isOn: isVisible}
}); })
OldMenu.shared.onMenuToggled = async (id: string, isOn: boolean) => { OldMenu.shared.onMenuToggled = async (id: string, isOn: boolean) => {
const property = selectProperties.find((o) => o.id === id) const property = selectProperties.find((o) => o.id === id)
@ -503,7 +503,7 @@ class BoardComponent extends React.Component<Props, State> {
newVisiblePropertyIds = [...activeView.visiblePropertyIds, id] newVisiblePropertyIds = [...activeView.visiblePropertyIds, id]
} }
await mutator.changeViewVisibleProperties(activeView, newVisiblePropertyIds) await mutator.changeViewVisibleProperties(activeView, newVisiblePropertyIds)
}; }
OldMenu.shared.showAtElement(e.target as HTMLElement) OldMenu.shared.showAtElement(e.target as HTMLElement)
} }
@ -513,14 +513,14 @@ class BoardComponent extends React.Component<Props, State> {
const selectProperties = boardTree.board.cardProperties.filter((o) => o.type === 'select') const selectProperties = boardTree.board.cardProperties.filter((o) => o.type === 'select')
OldMenu.shared.options = selectProperties.map((o) => { OldMenu.shared.options = selectProperties.map((o) => {
return {id: o.id, name: o.name} return {id: o.id, name: o.name}
}); })
OldMenu.shared.onMenuClicked = async (command: string) => { OldMenu.shared.onMenuClicked = async (command: string) => {
if (boardTree.activeView.groupById === command) { if (boardTree.activeView.groupById === command) {
return return
} }
await mutator.changeViewGroupById(boardTree.activeView, command) await mutator.changeViewGroupById(boardTree.activeView, command)
}; }
OldMenu.shared.showAtElement(e.target as HTMLElement) OldMenu.shared.showAtElement(e.target as HTMLElement)
} }
@ -567,7 +567,7 @@ class BoardComponent extends React.Component<Props, State> {
onSearchKeyDown(e: React.KeyboardEvent) { onSearchKeyDown(e: React.KeyboardEvent) {
if (e.keyCode === 27) { // ESC: Clear search if (e.keyCode === 27) { // ESC: Clear search
this.searchFieldRef.current.text = ''; this.searchFieldRef.current.text = ''
this.setState({...this.state, isSearching: false}) this.setState({...this.state, isSearching: false})
this.props.setSearchText(undefined) this.props.setSearchText(undefined)
e.preventDefault() e.preventDefault()

View File

@ -1,8 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
import './button.scss'; import './button.scss'
type Props = { type Props = {
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void onClick?: (e: React.MouseEvent<HTMLDivElement>) => void

View File

@ -1,24 +1,24 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
import {BlockIcons} from '../blockIcons'; import {BlockIcons} from '../blockIcons'
import {Block} from '../blocks/block'; import {Block} from '../blocks/block'
import {Card} from '../blocks/card'; import {Card} from '../blocks/card'
import {TextBlock} from '../blocks/textBlock'; import {TextBlock} from '../blocks/textBlock'
import {BoardTree} from '../boardTree'; import {BoardTree} from '../boardTree'
import {CardTree} from '../cardTree'; import {CardTree} from '../cardTree'
import {Menu as OldMenu, MenuOption} from '../menu'; import {Menu as OldMenu, MenuOption} from '../menu'
import mutator from '../mutator'; import mutator from '../mutator'
import {OctoListener} from '../octoListener'; import {OctoListener} from '../octoListener'
import {IBlock, IOrderedBlock} from '../octoTypes'; import {IBlock, IOrderedBlock} from '../octoTypes'
import {OctoUtils} from '../octoUtils'; import {OctoUtils} from '../octoUtils'
import {PropertyMenu} from '../propertyMenu'; import {PropertyMenu} from '../propertyMenu'
import {Utils} from '../utils'; import {Utils} from '../utils'
import Button from './button'; import Button from './button'
import {Editable} from './editable'; import {Editable} from './editable'
import {MarkdownEditor} from './markdownEditor'; import {MarkdownEditor} from './markdownEditor'
type Props = { type Props = {
boardTree: BoardTree boardTree: BoardTree
@ -45,7 +45,7 @@ export default class CardDetail extends React.Component<Props, State> {
this.cardListener.open(this.props.card.id, async () => { this.cardListener.open(this.props.card.id, async () => {
await cardTree.sync() await cardTree.sync()
this.setState({cardTree}) this.setState({cardTree})
}); })
const cardTree = new CardTree(this.props.card.id) const cardTree = new CardTree(this.props.card.id)
cardTree.sync().then(() => { cardTree.sync().then(() => {
this.setState({cardTree}) this.setState({cardTree})
@ -54,7 +54,7 @@ export default class CardDetail extends React.Component<Props, State> {
this.titleRef.current.focus() this.titleRef.current.focus()
} }
}, 0) }, 0)
}); })
} }
render() { render() {
@ -66,7 +66,7 @@ export default class CardDetail extends React.Component<Props, State> {
} }
const {comments} = cardTree const {comments} = cardTree
const newCommentPlaceholderText = 'Add a comment...'; const newCommentPlaceholderText = 'Add a comment...'
const backgroundRef = React.createRef<HTMLDivElement>() const backgroundRef = React.createRef<HTMLDivElement>()
const newCommentRef = React.createRef<Editable>() const newCommentRef = React.createRef<Editable>()
@ -145,8 +145,8 @@ export default class CardDetail extends React.Component<Props, State> {
const icon = card.icon const icon = card.icon
// TODO: Replace this placeholder // TODO: Replace this placeholder
const username = 'John Smith'; const username = 'John Smith'
const userImageUrl = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" style="fill: rgb(192, 192, 192);"><rect width="100" height="100" /></svg>'; const userImageUrl = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" style="fill: rgb(192, 192, 192);"><rect width="100" height="100" /></svg>'
return ( return (
<> <>
@ -205,28 +205,28 @@ export default class CardDetail extends React.Component<Props, State> {
menu.onNameChanged = (propertyName) => { menu.onNameChanged = (propertyName) => {
Utils.log('menu.onNameChanged') Utils.log('menu.onNameChanged')
mutator.renameProperty(board, propertyTemplate.id, propertyName) mutator.renameProperty(board, propertyTemplate.id, propertyName)
}; }
menu.onMenuClicked = async (command) => { menu.onMenuClicked = async (command) => {
switch (command) { switch (command) {
case 'type-text': case 'type-text':
await mutator.changePropertyType(board, propertyTemplate, 'text') await mutator.changePropertyType(board, propertyTemplate, 'text')
break; break
case 'type-number': case 'type-number':
await mutator.changePropertyType(board, propertyTemplate, 'number') await mutator.changePropertyType(board, propertyTemplate, 'number')
break; break
case 'type-createdTime': case 'type-createdTime':
await mutator.changePropertyType(board, propertyTemplate, 'createdTime') await mutator.changePropertyType(board, propertyTemplate, 'createdTime')
break; break
case 'type-updatedTime': case 'type-updatedTime':
await mutator.changePropertyType(board, propertyTemplate, 'updatedTime') await mutator.changePropertyType(board, propertyTemplate, 'updatedTime')
break; break
case 'type-select': case 'type-select':
await mutator.changePropertyType(board, propertyTemplate, 'select') await mutator.changePropertyType(board, propertyTemplate, 'select')
break; break
case 'delete': case 'delete':
await mutator.deleteProperty(boardTree, propertyTemplate.id) await mutator.deleteProperty(boardTree, propertyTemplate.id)
break; break
default: default:
Utils.assertFailure(`Unhandled menu id: ${command}`) Utils.assertFailure(`Unhandled menu id: ${command}`)
} }
@ -263,12 +263,12 @@ export default class CardDetail extends React.Component<Props, State> {
switch (id) { switch (id) {
case 'delete': { case 'delete': {
mutator.deleteBlock(activeComment) mutator.deleteBlock(activeComment)
break; break
} }
} }
} }
OldMenu.shared.showAtElement(e.target as HTMLElement) OldMenu.shared.showAtElement(e.target as HTMLElement)
}; }
return (<div return (<div
key={comment.id} key={comment.id}
@ -277,7 +277,7 @@ export default class CardDetail extends React.Component<Props, State> {
optionsButtonRef.current.style.display = null optionsButtonRef.current.style.display = null
}} }}
onMouseLeave={() => { onMouseLeave={() => {
optionsButtonRef.current.style.display = 'none'; optionsButtonRef.current.style.display = 'none'
}} }}
> >
<div className='comment-header'> <div className='comment-header'>
@ -317,7 +317,7 @@ export default class CardDetail extends React.Component<Props, State> {
}} }}
onBlur={() => { onBlur={() => {
if (!newCommentRef.current.text) { if (!newCommentRef.current.text) {
sendCommentButtonRef.current.style.display = 'none'; sendCommentButtonRef.current.style.display = 'none'
} }
}} }}
onKeyDown={(e) => { onKeyDown={(e) => {
@ -366,14 +366,14 @@ export default class CardDetail extends React.Component<Props, State> {
const order = cardTree.contents.length * 1000 const order = cardTree.contents.length * 1000
const block = new Block({type: 'text', parentId: card.id, order}) const block = new Block({type: 'text', parentId: card.id, order})
await mutator.insertBlock(block, 'add text') await mutator.insertBlock(block, 'add text')
break; break
case 'image': case 'image':
Utils.selectLocalFile( Utils.selectLocalFile(
(file) => { (file) => {
mutator.createImageBlock(card.id, file, cardTree.contents.length * 1000) mutator.createImageBlock(card.id, file, cardTree.contents.length * 1000)
}, },
'.jpg,.jpeg,.png') '.jpg,.jpeg,.png')
break; break
} }
} }
OldMenu.shared.showAtElement(e.target as HTMLElement) OldMenu.shared.showAtElement(e.target as HTMLElement)
@ -427,7 +427,7 @@ export default class CardDetail extends React.Component<Props, State> {
const newOrder = OctoUtils.getOrderBefore(previousBlock, cardTree.contents) const newOrder = OctoUtils.getOrderBefore(previousBlock, cardTree.contents)
Utils.log(`moveUp ${newOrder}`) Utils.log(`moveUp ${newOrder}`)
mutator.changeOrder(block, newOrder, 'move up') mutator.changeOrder(block, newOrder, 'move up')
break; break
} }
case 'moveDown': { case 'moveDown': {
if (index >= cardTree.contents.length - 1) { if (index >= cardTree.contents.length - 1) {
@ -437,7 +437,7 @@ export default class CardDetail extends React.Component<Props, State> {
const newOrder = OctoUtils.getOrderAfter(nextBlock, cardTree.contents) const newOrder = OctoUtils.getOrderAfter(nextBlock, cardTree.contents)
Utils.log(`moveDown ${newOrder}`) Utils.log(`moveDown ${newOrder}`)
mutator.changeOrder(block, newOrder, 'move down') mutator.changeOrder(block, newOrder, 'move down')
break; break
} }
case 'insertAbove-text': { case 'insertAbove-text': {
const newBlock = new TextBlock({parentId: card.id}) const newBlock = new TextBlock({parentId: card.id})
@ -446,7 +446,7 @@ export default class CardDetail extends React.Component<Props, State> {
newBlock.order = OctoUtils.getOrderBefore(block, cardTree.contents) newBlock.order = OctoUtils.getOrderBefore(block, cardTree.contents)
Utils.log(`insert block ${block.id}, order: ${block.order}`) Utils.log(`insert block ${block.id}, order: ${block.order}`)
mutator.insertBlock(newBlock, 'insert card text') mutator.insertBlock(newBlock, 'insert card text')
break; break
} }
case 'insertAbove-image': { case 'insertAbove-image': {
Utils.selectLocalFile( Utils.selectLocalFile(
@ -455,11 +455,11 @@ export default class CardDetail extends React.Component<Props, State> {
}, },
'.jpg,.jpeg,.png') '.jpg,.jpeg,.png')
break; break
} }
case 'delete': { case 'delete': {
mutator.deleteBlock(block) mutator.deleteBlock(block)
break; break
} }
} }
} }
@ -477,11 +477,11 @@ export default class CardDetail extends React.Component<Props, State> {
switch (optionId) { switch (optionId) {
case 'remove': case 'remove':
mutator.changeIcon(card, undefined, 'remove icon') mutator.changeIcon(card, undefined, 'remove icon')
break; break
case 'random': case 'random':
const newIcon = BlockIcons.shared.randomIcon() const newIcon = BlockIcons.shared.randomIcon()
mutator.changeIcon(card, newIcon) mutator.changeIcon(card, newIcon)
break; break
} }
} }
OldMenu.shared.showAtElement(e.target as HTMLElement) OldMenu.shared.showAtElement(e.target as HTMLElement)

View File

@ -1,14 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
import {Card} from '../blocks/card'; import {Card} from '../blocks/card'
import {BoardTree} from '../boardTree'; import {BoardTree} from '../boardTree'
import mutator from '../mutator'; import mutator from '../mutator'
import Menu from '../widgets/menu'; import Menu from '../widgets/menu'
import CardDetail from './cardDetail'; import CardDetail from './cardDetail'
import Dialog from './dialog'; import Dialog from './dialog'
type Props = { type Props = {
boardTree: BoardTree boardTree: BoardTree

View File

@ -1,10 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
import MenuWrapper from '../widgets/menuWrapper'; import MenuWrapper from '../widgets/menuWrapper'
import Button from './button'; import Button from './button'
type Props = { type Props = {
children: React.ReactNode children: React.ReactNode

View File

@ -1,8 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
import {Utils} from '../utils'; import {Utils} from '../utils'
type Props = { type Props = {
onChanged: (text: string) => void onChanged: (text: string) => void
@ -36,23 +36,23 @@ class Editable extends React.Component<Props, State> {
const {isMarkdown} = this.props const {isMarkdown} = this.props
if (!value) { if (!value) {
this.elementRef.current.innerText = ''; this.elementRef.current.innerText = ''
} else { } else {
this.elementRef.current.innerHTML = isMarkdown ? Utils.htmlFromMarkdown(value) : Utils.htmlEncode(value) this.elementRef.current.innerHTML = isMarkdown ? Utils.htmlFromMarkdown(value) : Utils.htmlEncode(value)
} }
this._text = value || ''; this._text = value || ''
} }
private elementRef = React.createRef<HTMLDivElement>() private elementRef = React.createRef<HTMLDivElement>()
constructor(props: Props) { constructor(props: Props) {
super(props) super(props)
this._text = props.text || ''; this._text = props.text || ''
} }
componentDidUpdate(prevPros: Props, prevState: State) { componentDidUpdate(prevPros: Props, prevState: State) {
this._text = this.props.text || ''; this._text = this.props.text || ''
} }
focus() { focus() {
@ -76,7 +76,7 @@ class Editable extends React.Component<Props, State> {
if (text) { if (text) {
html = isMarkdown ? Utils.htmlFromMarkdown(text) : Utils.htmlEncode(text) html = isMarkdown ? Utils.htmlFromMarkdown(text) : Utils.htmlEncode(text)
} else { } else {
html = ''; html = ''
} }
const element = const element =
@ -102,7 +102,7 @@ class Editable extends React.Component<Props, State> {
onBlur={async () => { onBlur={async () => {
const newText = this.elementRef.current.innerText const newText = this.elementRef.current.innerText
const oldText = this.props.text || ''; const oldText = this.props.text || ''
if (newText !== oldText && onChanged) { if (newText !== oldText && onChanged) {
onChanged(newText) onChanged(newText)
} }

View File

@ -1,13 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
import {BoardTree} from '../boardTree'; import {BoardTree} from '../boardTree'
import {FilterClause, FilterCondition} from '../filterClause'; import {FilterClause, FilterCondition} from '../filterClause'
import {FilterGroup} from '../filterGroup'; import {FilterGroup} from '../filterGroup'
import {Menu} from '../menu'; import {Menu} from '../menu'
import mutator from '../mutator'; import mutator from '../mutator'
import {Utils} from '../utils'; import {Utils} from '../utils'
type Props = { type Props = {
boardTree: BoardTree boardTree: BoardTree
@ -42,7 +42,7 @@ class FilterComponent extends React.Component<Props> {
> >
{filters.map((filter) => { {filters.map((filter) => {
const template = board.cardProperties.find((o) => o.id === filter.propertyId) const template = board.cardProperties.find((o) => o.id === filter.propertyId)
const propertyName = template ? template.name : '(unknown)'; // TODO: Handle error const propertyName = template ? template.name : '(unknown)' // TODO: Handle error
const key = `${filter.propertyId}-${filter.condition}-${filter.values.join(',')}` const key = `${filter.propertyId}-${filter.condition}-${filter.values.join(',')}`
Utils.log(`FilterClause key: ${key}`) Utils.log(`FilterClause key: ${key}`)
return (<div return (<div

View File

@ -1,10 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import EasyMDE from 'easymde'; import EasyMDE from 'easymde'
import React from 'react'; import React from 'react'
import SimpleMDE from 'react-simplemde-editor'; import SimpleMDE from 'react-simplemde-editor'
import {Utils} from '../utils'; import {Utils} from '../utils'
type Props = { type Props = {
onChanged: (text: string) => void onChanged: (text: string) => void
@ -43,7 +43,7 @@ class MarkdownEditor extends React.Component<Props, State> {
} }
componentDidUpdate(prevPros: Props, prevState: State) { componentDidUpdate(prevPros: Props, prevState: State) {
this.text = this.props.text || ''; this.text = this.props.text || ''
} }
showEditor() { showEditor() {
@ -123,7 +123,7 @@ class MarkdownEditor extends React.Component<Props, State> {
events={{ events={{
blur: () => { blur: () => {
const newText = this.elementRef.current.state.value const newText = this.elementRef.current.state.value
const oldText = this.props.text || ''; const oldText = this.props.text || ''
if (newText !== oldText && onChanged) { if (newText !== oldText && onChanged) {
const newHtml = newText ? Utils.htmlFromMarkdown(newText) : Utils.htmlFromMarkdown(placeholderText || '') const newHtml = newText ? Utils.htmlFromMarkdown(newText) : Utils.htmlFromMarkdown(placeholderText || '')
this.previewRef.current.innerHTML = newHtml this.previewRef.current.innerHTML = newHtml

View File

@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
type Props = { type Props = {
} }

View File

@ -1,21 +1,21 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
import {render} from '@testing-library/react'; import {render} from '@testing-library/react'
import '@testing-library/jest-dom'; import '@testing-library/jest-dom'
import RootPortal from './rootPortal'; import RootPortal from './rootPortal'
describe('components/RootPortal', () => { describe('components/RootPortal', () => {
beforeEach(() => { beforeEach(() => {
// Quick fix to disregard console error when unmounting a component // Quick fix to disregard console error when unmounting a component
console.error = jest.fn() console.error = jest.fn()
}); })
test('should match snapshot', () => { test('should match snapshot', () => {
const rootPortalDiv = document.createElement('div') const rootPortalDiv = document.createElement('div')
rootPortalDiv.id = 'root-portal'; rootPortalDiv.id = 'root-portal'
const {getByText, container} = render( const {getByText, container} = render(
<RootPortal> <RootPortal>
@ -26,5 +26,5 @@ describe('components/RootPortal', () => {
expect(getByText('Testing Portal')).toBeVisible() expect(getByText('Testing Portal')).toBeVisible()
expect(container).toMatchSnapshot() expect(container).toMatchSnapshot()
}); })
}) })

View File

@ -1,9 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'; import PropTypes from 'prop-types'
type Props = { type Props = {
children: React.ReactNode children: React.ReactNode

View File

@ -1,14 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
import {Archiver} from '../archiver'; import {Archiver} from '../archiver'
import {Board} from '../blocks/board'; import {Board} from '../blocks/board'
import {BoardTree} from '../boardTree'; import {BoardTree} from '../boardTree'
import Menu from '../widgets/menu'; import Menu from '../widgets/menu'
import MenuWrapper from '../widgets/menuWrapper'; import MenuWrapper from '../widgets/menuWrapper'
import mutator from '../mutator'; import mutator from '../mutator'
import {WorkspaceTree} from '../workspaceTree'; import {WorkspaceTree} from '../workspaceTree'
type Props = { type Props = {
showBoard: (id: string) => void showBoard: (id: string) => void
@ -29,7 +29,7 @@ class Sidebar extends React.Component<Props> {
<div className='octo-sidebar'> <div className='octo-sidebar'>
{ {
boards.map((board) => { boards.map((board) => {
const displayTitle = board.title || '(Untitled Board)'; const displayTitle = board.title || '(Untitled Board)'
return ( return (
<div <div
key={board.id} key={board.id}

View File

@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
type Props = { type Props = {
onChanged: (isOn: boolean) => void onChanged: (isOn: boolean) => void
@ -39,7 +39,7 @@ class Switch extends React.Component<Props, State> {
const {style} = this.props const {style} = this.props
const {isOn} = this.state const {isOn} = this.state
const className = isOn ? 'octo-switch on' : 'octo-switch'; const className = isOn ? 'octo-switch on' : 'octo-switch'
const element = const element =
(<div (<div
ref={this.elementRef} ref={this.elementRef}

View File

@ -1,26 +1,26 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
import {Archiver} from '../archiver'; import {Archiver} from '../archiver'
import {BlockIcons} from '../blockIcons'; import {BlockIcons} from '../blockIcons'
import {IPropertyTemplate} from '../blocks/board'; import {IPropertyTemplate} from '../blocks/board'
import {Card} from '../blocks/card'; import {Card} from '../blocks/card'
import {BoardTree} from '../boardTree'; import {BoardTree} from '../boardTree'
import ViewMenu from '../components/viewMenu'; import ViewMenu from '../components/viewMenu'
import {CsvExporter} from '../csvExporter'; import {CsvExporter} from '../csvExporter'
import {Menu as OldMenu} from '../menu'; import {Menu as OldMenu} from '../menu'
import mutator from '../mutator'; import mutator from '../mutator'
import {OctoUtils} from '../octoUtils'; import {OctoUtils} from '../octoUtils'
import {Utils} from '../utils'; import {Utils} from '../utils'
import Menu from '../widgets/menu'; import Menu from '../widgets/menu'
import MenuWrapper from '../widgets/menuWrapper'; import MenuWrapper from '../widgets/menuWrapper'
import Button from './button'; import Button from './button'
import {CardDialog} from './cardDialog'; import {CardDialog} from './cardDialog'
import {Editable} from './editable'; import {Editable} from './editable'
import RootPortal from './rootPortal'; import RootPortal from './rootPortal'
import {TableRow} from './tableRow'; import {TableRow} from './tableRow'
type Props = { type Props = {
boardTree?: BoardTree boardTree?: BoardTree
@ -322,7 +322,7 @@ class TableComponent extends React.Component<Props, State> {
OldMenu.shared.options = selectProperties.map((o) => { OldMenu.shared.options = selectProperties.map((o) => {
const isVisible = activeView.visiblePropertyIds.includes(o.id) const isVisible = activeView.visiblePropertyIds.includes(o.id)
return {id: o.id, name: o.name, type: 'switch', isOn: isVisible} return {id: o.id, name: o.name, type: 'switch', isOn: isVisible}
}); })
OldMenu.shared.onMenuToggled = async (id: string, isOn: boolean) => { OldMenu.shared.onMenuToggled = async (id: string, isOn: boolean) => {
const property = selectProperties.find((o) => o.id === id) const property = selectProperties.find((o) => o.id === id)
@ -336,7 +336,7 @@ class TableComponent extends React.Component<Props, State> {
newVisiblePropertyIds = [...activeView.visiblePropertyIds, id] newVisiblePropertyIds = [...activeView.visiblePropertyIds, id]
} }
await mutator.changeViewVisibleProperties(activeView, newVisiblePropertyIds) await mutator.changeViewVisibleProperties(activeView, newVisiblePropertyIds)
}; }
OldMenu.shared.showAtElement(e.target as HTMLElement) OldMenu.shared.showAtElement(e.target as HTMLElement)
} }
@ -356,11 +356,11 @@ class TableComponent extends React.Component<Props, State> {
switch (id) { switch (id) {
case 'exportCsv': { case 'exportCsv': {
CsvExporter.exportTableCsv(boardTree) CsvExporter.exportTableCsv(boardTree)
break; break
} }
case 'exportBoardArchive': { case 'exportBoardArchive': {
Archiver.exportBoardTree(boardTree) Archiver.exportBoardTree(boardTree)
break; break
} }
} }
} }
@ -393,14 +393,14 @@ class TableComponent extends React.Component<Props, State> {
{propertyId: templateId, reversed: false}, {propertyId: templateId, reversed: false},
] ]
await mutator.changeViewSortOptions(activeView, newSortOptions) await mutator.changeViewSortOptions(activeView, newSortOptions)
break; break
} }
case 'sortDescending': { case 'sortDescending': {
const newSortOptions = [ const newSortOptions = [
{propertyId: templateId, reversed: true}, {propertyId: templateId, reversed: true},
] ]
await mutator.changeViewSortOptions(activeView, newSortOptions) await mutator.changeViewSortOptions(activeView, newSortOptions)
break; break
} }
case 'insertLeft': { case 'insertLeft': {
if (templateId !== '__name') { if (templateId !== '__name') {
@ -422,20 +422,20 @@ class TableComponent extends React.Component<Props, State> {
} }
case 'duplicate': { case 'duplicate': {
await mutator.duplicatePropertyTemplate(boardTree, templateId) await mutator.duplicatePropertyTemplate(boardTree, templateId)
break; break
} }
case 'hide': { case 'hide': {
const newVisiblePropertyIds = activeView.visiblePropertyIds.filter((o) => o !== templateId) const newVisiblePropertyIds = activeView.visiblePropertyIds.filter((o) => o !== templateId)
await mutator.changeViewVisibleProperties(activeView, newVisiblePropertyIds) await mutator.changeViewVisibleProperties(activeView, newVisiblePropertyIds)
break; break
} }
case 'delete': { case 'delete': {
await mutator.deleteProperty(boardTree, templateId) await mutator.deleteProperty(boardTree, templateId)
break; break
} }
default: { default: {
Utils.assertFailure(`Unexpected menu option: ${optionId}`) Utils.assertFailure(`Unexpected menu option: ${optionId}`)
break; break
} }
} }
} }
@ -489,7 +489,7 @@ class TableComponent extends React.Component<Props, State> {
onSearchKeyDown(e: React.KeyboardEvent) { onSearchKeyDown(e: React.KeyboardEvent) {
if (e.keyCode === 27) { // ESC: Clear search if (e.keyCode === 27) { // ESC: Clear search
this.searchFieldRef.current.text = ''; this.searchFieldRef.current.text = ''
this.setState({...this.state, isSearching: false}) this.setState({...this.state, isSearching: false})
this.props.setSearchText(undefined) this.props.setSearchText(undefined)
e.preventDefault() e.preventDefault()

View File

@ -1,15 +1,15 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
import {BoardTree} from '../boardTree'; import {BoardTree} from '../boardTree'
import {Card} from '../blocks/card'; import {Card} from '../blocks/card'
import mutator from '../mutator'; import mutator from '../mutator'
import {OctoUtils} from '../octoUtils'; import {OctoUtils} from '../octoUtils'
import {Editable} from './editable'; import {Editable} from './editable'
import {CardDialog} from './cardDialog'; import {CardDialog} from './cardDialog'
import RootPortal from './rootPortal'; import RootPortal from './rootPortal'
type Props = { type Props = {
boardTree: BoardTree boardTree: BoardTree
@ -54,7 +54,7 @@ class TableRow extends React.Component<Props, State> {
openButonRef.current.style.display = null openButonRef.current.style.display = null
}} }}
onMouseLeave={() => { onMouseLeave={() => {
openButonRef.current.style.display = 'none'; openButonRef.current.style.display = 'none'
}} }}
> >
<div className='octo-icontitle'> <div className='octo-icontitle'>

View File

@ -1,13 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
import {Board} from '../blocks/board'; import {Board} from '../blocks/board'
import {BoardView} from '../blocks/boardView'; import {BoardView} from '../blocks/boardView'
import {BoardTree} from '../boardTree'; import {BoardTree} from '../boardTree'
import mutator from '../mutator'; import mutator from '../mutator'
import {Utils} from '../utils'; import {Utils} from '../utils'
import Menu from '../widgets/menu'; import Menu from '../widgets/menu'
type Props = { type Props = {
boardTree?: BoardTree boardTree?: BoardTree
@ -36,8 +36,8 @@ export default class ViewMenu extends React.Component<Props> {
const {board, boardTree, showView} = this.props const {board, boardTree, showView} = this.props
Utils.log('addview-board') Utils.log('addview-board')
const view = new BoardView() const view = new BoardView()
view.title = 'Board View'; view.title = 'Board View'
view.viewType = 'board'; view.viewType = 'board'
view.parentId = board.id view.parentId = board.id
const oldViewId = boardTree.activeView.id const oldViewId = boardTree.activeView.id
@ -50,7 +50,7 @@ export default class ViewMenu extends React.Component<Props> {
}, },
async () => { async () => {
showView(oldViewId) showView(oldViewId)
}); })
} }
handleAddViewTable = async (id: string) => { handleAddViewTable = async (id: string) => {
@ -58,8 +58,8 @@ export default class ViewMenu extends React.Component<Props> {
Utils.log('addview-table') Utils.log('addview-table')
const view = new BoardView() const view = new BoardView()
view.title = 'Table View'; view.title = 'Table View'
view.viewType = 'table'; view.viewType = 'table'
view.parentId = board.id view.parentId = board.id
view.visiblePropertyIds = board.cardProperties.map((o) => o.id) view.visiblePropertyIds = board.cardProperties.map((o) => o.id)
@ -73,7 +73,7 @@ export default class ViewMenu extends React.Component<Props> {
}, },
async () => { async () => {
showView(oldViewId) showView(oldViewId)
}); })
} }
render() { render() {

View File

@ -1,14 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
import {BoardTree} from '../boardTree'; import {BoardTree} from '../boardTree'
import {Utils} from '../utils'; import {Utils} from '../utils'
import {WorkspaceTree} from '../workspaceTree'; import {WorkspaceTree} from '../workspaceTree'
import {BoardComponent} from './boardComponent'; import {BoardComponent} from './boardComponent'
import {Sidebar} from './sidebar'; import {Sidebar} from './sidebar'
import {TableComponent} from './tableComponent'; import {TableComponent} from './tableComponent'
type Props = { type Props = {
workspaceTree: WorkspaceTree workspaceTree: WorkspaceTree
@ -52,7 +52,7 @@ class WorkspaceComponent extends React.Component<Props> {
showFilter={showFilter} showFilter={showFilter}
setSearchText={setSearchText} setSearchText={setSearchText}
showView={showView} showView={showView}
/>); />)
} }
case 'table': { case 'table': {
@ -61,7 +61,7 @@ class WorkspaceComponent extends React.Component<Props> {
showFilter={showFilter} showFilter={showFilter}
setSearchText={setSearchText} setSearchText={setSearchText}
showView={showView} showView={showView}
/>); />)
} }
default: { default: {

View File

@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {MenuOption} from './menu'; import {MenuOption} from './menu'
class Constants { class Constants {
static menuColors: MenuOption[] = [ static menuColors: MenuOption[] = [

View File

@ -1,9 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {BoardView} from './blocks/boardView'; import {BoardView} from './blocks/boardView'
import {BoardTree} from './boardTree'; import {BoardTree} from './boardTree'
import {OctoUtils} from './octoUtils'; import {OctoUtils} from './octoUtils'
import {Utils} from './utils'; import {Utils} from './utils'
class CsvExporter { class CsvExporter {
static exportTableCsv(boardTree: BoardTree, view?: BoardView) { static exportTableCsv(boardTree: BoardTree, view?: BoardView) {
@ -12,17 +12,17 @@ class CsvExporter {
const rows = CsvExporter.generateTableArray(boardTree, view) const rows = CsvExporter.generateTableArray(boardTree, view)
let csvContent = 'data:text/csv;charset=utf-8,'; let csvContent = 'data:text/csv;charset=utf-8,'
rows.forEach((row) => { rows.forEach((row) => {
const encodedRow = row.join(',') const encodedRow = row.join(',')
csvContent += encodedRow + '\r\n'; csvContent += encodedRow + '\r\n'
}) })
const filename = `${Utils.sanitizeFilename(viewToExport.title)}.csv` const filename = `${Utils.sanitizeFilename(viewToExport.title)}.csv`
const encodedUri = encodeURI(csvContent) const encodedUri = encodeURI(csvContent)
const link = document.createElement('a') const link = document.createElement('a')
link.style.display = 'none'; link.style.display = 'none'
link.setAttribute('href', encodedUri) link.setAttribute('href', encodedUri)
link.setAttribute('download', filename) link.setAttribute('download', filename)
document.body.appendChild(link) // FireFox support document.body.appendChild(link) // FireFox support
@ -44,7 +44,7 @@ class CsvExporter {
const row: string[] = [] const row: string[] = []
visibleProperties.forEach((template) => { visibleProperties.forEach((template) => {
row.push(template.name) row.push(template.name)
}); })
rows.push(row) rows.push(row)
} }
@ -52,7 +52,7 @@ class CsvExporter {
const row: string[] = [] const row: string[] = []
visibleProperties.forEach((template) => { visibleProperties.forEach((template) => {
const propertyValue = card.properties[template.id] const propertyValue = card.properties[template.id]
const displayValue = OctoUtils.propertyDisplayValue(card, propertyValue, template) || ''; const displayValue = OctoUtils.propertyDisplayValue(card, propertyValue, template) || ''
if (template.type === 'number') { if (template.type === 'number') {
const numericValue = propertyValue ? Number(propertyValue).toString() : undefined const numericValue = propertyValue ? Number(propertyValue).toString() : undefined
row.push(numericValue) row.push(numericValue)
@ -62,7 +62,7 @@ class CsvExporter {
} }
}) })
rows.push(row) rows.push(row)
}); })
return rows return rows
} }

View File

@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {Utils} from './utils'; import {Utils} from './utils'
type FilterCondition = 'includes' | 'notIncludes' | 'isEmpty' | 'isNotEmpty' type FilterCondition = 'includes' | 'notIncludes' | 'isEmpty' | 'isNotEmpty'
@ -11,20 +11,20 @@ class FilterClause {
static filterConditionDisplayString(filterCondition: FilterCondition) { static filterConditionDisplayString(filterCondition: FilterCondition) {
switch (filterCondition) { switch (filterCondition) {
case 'includes': return 'includes'; case 'includes': return 'includes'
case 'notIncludes': return "doesn't include" case 'notIncludes': return "doesn't include"
case 'isEmpty': return 'is empty'; case 'isEmpty': return 'is empty'
case 'isNotEmpty': return 'is not empty'; case 'isNotEmpty': return 'is not empty'
default: { default: {
Utils.assertFailure() Utils.assertFailure()
return '(unknown)'; return '(unknown)'
} }
} }
} }
constructor(o: any = {}) { constructor(o: any = {}) {
this.propertyId = o.propertyId || ''; this.propertyId = o.propertyId || ''
this.condition = o.condition || 'includes'; this.condition = o.condition || 'includes'
this.values = o.values?.slice() || [] this.values = o.values?.slice() || []
} }

View File

@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {FilterClause} from './filterClause'; import {FilterClause} from './filterClause'
type FilterGroupOperation = 'and' | 'or' type FilterGroupOperation = 'and' | 'or'
@ -14,7 +14,7 @@ class FilterGroup {
} }
constructor(o: any = {}) { constructor(o: any = {}) {
this.operation = o.operation || 'and'; this.operation = o.operation || 'and'
this.filters = o.filters ? this.filters = o.filters ?
o.filters.map((p: any) => { o.filters.map((p: any) => {
if (FilterGroup.isAnInstanceOf(p)) { if (FilterGroup.isAnInstanceOf(p)) {

View File

@ -1,8 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom'
import App from './app'; import App from './app'
ReactDOM.render(<App/>, document.getElementById('octo-tasks-app')) ReactDOM.render(<App/>, document.getElementById('octo-tasks-app'))

View File

@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {Utils} from './utils'; import {Utils} from './utils'
type MenuOption = { type MenuOption = {
id: string, id: string,
@ -56,7 +56,7 @@ class Menu {
const bodyRect = document.body.getBoundingClientRect() const bodyRect = document.body.getBoundingClientRect()
const rect = optionElement.getBoundingClientRect() const rect = optionElement.getBoundingClientRect()
this.showSubMenu(rect.right - bodyRect.left, rect.top - bodyRect.top, option.id) this.showSubMenu(rect.right - bodyRect.left, rect.top - bodyRect.top, option.id)
}; }
} else { } else {
if (option.icon) { if (option.icon) {
let iconName: string let iconName: string
@ -73,7 +73,7 @@ class Menu {
optionElement.onmouseenter = () => { optionElement.onmouseenter = () => {
this.hideSubMenu() this.hideSubMenu()
}; }
optionElement.onclick = (e) => { optionElement.onclick = (e) => {
if (this.onMenuClicked) { if (this.onMenuClicked) {
this.onMenuClicked(option.id, option.type) this.onMenuClicked(option.id, option.type)
@ -81,14 +81,14 @@ class Menu {
this.hide() this.hide()
e.stopPropagation() e.stopPropagation()
return false return false
}; }
} }
if (option.type === 'color') { if (option.type === 'color') {
const colorbox = optionElement.insertBefore(Utils.htmlToElement('<div class="menu-colorbox"></div>'), optionElement.firstChild) const colorbox = optionElement.insertBefore(Utils.htmlToElement('<div class="menu-colorbox"></div>'), optionElement.firstChild)
colorbox.classList.add(option.id) // id is the css class name for the color colorbox.classList.add(option.id) // id is the css class name for the color
} else if (option.type === 'switch') { } else if (option.type === 'switch') {
const className = option.isOn ? 'octo-switch on' : 'octo-switch'; const className = option.isOn ? 'octo-switch on' : 'octo-switch'
const switchElement = optionElement.appendChild(Utils.htmlToElement(`<div class="${className}"></div>`)) const switchElement = optionElement.appendChild(Utils.htmlToElement(`<div class="${className}"></div>`))
switchElement.appendChild(Utils.htmlToElement('<div class="octo-switch-inner"></div>')) switchElement.appendChild(Utils.htmlToElement('<div class="octo-switch-inner"></div>'))
switchElement.onclick = (e) => { switchElement.onclick = (e) => {
@ -104,7 +104,7 @@ class Menu {
} }
e.stopPropagation() e.stopPropagation()
return false return false
}; }
optionElement.onclick = null optionElement.onclick = null
} }
} }
@ -132,7 +132,7 @@ class Menu {
this.onBodyClick = (e: MouseEvent) => { this.onBodyClick = (e: MouseEvent) => {
console.log('onBodyClick') console.log('onBodyClick')
this.hide() this.hide()
}; }
this.onBodyKeyDown = (e: KeyboardEvent) => { this.onBodyKeyDown = (e: KeyboardEvent) => {
console.log(`onBodyKeyDown, target: ${e.target}`) console.log(`onBodyKeyDown, target: ${e.target}`)
@ -199,7 +199,7 @@ class Menu {
this.onMenuClicked(subMenuId, type) this.onMenuClicked(subMenuId, type)
} }
this.hide() this.hide()
}; }
this.subMenu.options = options this.subMenu.options = options
this.subMenu.showAt(pageX, pageY) this.subMenu.showAt(pageX, pageY)

View File

@ -1,16 +1,16 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {Block} from './blocks/block'; import {Block} from './blocks/block'
import {Board, IPropertyOption, IPropertyTemplate, PropertyType} from './blocks/board'; import {Board, IPropertyOption, IPropertyTemplate, PropertyType} from './blocks/board'
import {BoardView, ISortOption} from './blocks/boardView'; import {BoardView, ISortOption} from './blocks/boardView'
import {Card} from './blocks/card'; import {Card} from './blocks/card'
import {ImageBlock} from './blocks/imageBlock'; import {ImageBlock} from './blocks/imageBlock'
import {BoardTree} from './boardTree'; import {BoardTree} from './boardTree'
import {FilterGroup} from './filterGroup'; import {FilterGroup} from './filterGroup'
import octoClient from './octoClient'; import octoClient from './octoClient'
import {IBlock, IOrderedBlock} from './octoTypes'; import {IBlock, IOrderedBlock} from './octoTypes'
import undoManager from './undomanager'; import undoManager from './undomanager'
import {Utils} from './utils'; import {Utils} from './utils'
// //
// The Mutator is used to make all changes to server state // The Mutator is used to make all changes to server state
@ -133,14 +133,14 @@ class Mutator {
const changedBlocks: IBlock[] = [board] const changedBlocks: IBlock[] = [board]
board.cardProperties.splice(index, 0, template) board.cardProperties.splice(index, 0, template)
let description = 'add property'; let description = 'add property'
if (activeView.viewType === 'table') { if (activeView.viewType === 'table') {
oldBlocks.push(new BoardView(activeView)) oldBlocks.push(new BoardView(activeView))
activeView.visiblePropertyIds.push(template.id) activeView.visiblePropertyIds.push(template.id)
changedBlocks.push(activeView) changedBlocks.push(activeView)
description = 'add column'; description = 'add column'
} }
await undoManager.perform( await undoManager.perform(
@ -173,13 +173,13 @@ class Mutator {
} }
board.cardProperties.splice(index + 1, 0, newTemplate) board.cardProperties.splice(index + 1, 0, newTemplate)
let description = 'duplicate property'; let description = 'duplicate property'
if (activeView.viewType === 'table') { if (activeView.viewType === 'table') {
oldBlocks.push(new BoardView(activeView)) oldBlocks.push(new BoardView(activeView))
activeView.visiblePropertyIds.push(newTemplate.id) activeView.visiblePropertyIds.push(newTemplate.id)
changedBlocks.push(activeView) changedBlocks.push(activeView)
description = 'duplicate column'; description = 'duplicate column'
} }
await undoManager.perform( await undoManager.perform(

View File

@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {IBlock} from './octoTypes'; import {IBlock} from './octoTypes'
import {Utils} from './utils'; import {Utils} from './utils'
// //
// OctoClient is the client interface to the server APIs // OctoClient is the client interface to the server APIs
@ -23,7 +23,7 @@ class OctoClient {
} }
async exportFullArchive(): Promise<IBlock[]> { async exportFullArchive(): Promise<IBlock[]> {
const path = '/api/v1/blocks/export'; const path = '/api/v1/blocks/export'
const response = await fetch(this.serverUrl + path) const response = await fetch(this.serverUrl + path)
const blocks = (await response.json() || []) as IBlock[] const blocks = (await response.json() || []) as IBlock[]
this.fixBlocks(blocks) this.fixBlocks(blocks)
@ -34,7 +34,7 @@ class OctoClient {
Utils.log(`importFullArchive: ${blocks.length} blocks(s)`) Utils.log(`importFullArchive: ${blocks.length} blocks(s)`)
blocks.forEach((block) => { blocks.forEach((block) => {
Utils.log(`\t ${block.type}, ${block.id}`) Utils.log(`\t ${block.type}, ${block.id}`)
}); })
const body = JSON.stringify(blocks) const body = JSON.stringify(blocks)
return await fetch(this.serverUrl + '/api/v1/blocks/import', { return await fetch(this.serverUrl + '/api/v1/blocks/import', {
method: 'POST', method: 'POST',
@ -55,7 +55,7 @@ class OctoClient {
} else if (type) { } else if (type) {
path = `/api/v1/blocks?type=${encodeURIComponent(type)}` path = `/api/v1/blocks?type=${encodeURIComponent(type)}`
} else { } else {
path = '/api/v1/blocks'; path = '/api/v1/blocks'
} }
const response = await fetch(this.serverUrl + path) const response = await fetch(this.serverUrl + path)
@ -99,7 +99,7 @@ class OctoClient {
const now = Date.now() const now = Date.now()
blocks.forEach((block) => { blocks.forEach((block) => {
block.updateAt = now block.updateAt = now
}); })
return await this.insertBlocks(blocks) return await this.insertBlocks(blocks)
} }
@ -122,7 +122,7 @@ class OctoClient {
Utils.log(`insertBlocks: ${blocks.length} blocks(s)`) Utils.log(`insertBlocks: ${blocks.length} blocks(s)`)
blocks.forEach((block) => { blocks.forEach((block) => {
Utils.log(`\t ${block.type}, ${block.id}`) Utils.log(`\t ${block.type}, ${block.id}`)
}); })
const body = JSON.stringify(blocks) const body = JSON.stringify(blocks)
return await fetch(this.serverUrl + '/api/v1/blocks', { return await fetch(this.serverUrl + '/api/v1/blocks', {
method: 'POST', method: 'POST',

View File

@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {Utils} from './utils'; import {Utils} from './utils'
// //
// OctoListener calls a handler when a block or any of its children changes // OctoListener calls a handler when a block or any of its children changes
@ -36,11 +36,11 @@ class OctoListener {
ws.onopen = () => { ws.onopen = () => {
Utils.log('OctoListener webSocket opened.') Utils.log('OctoListener webSocket opened.')
ws.send('{}') ws.send('{}')
}; }
ws.onerror = (e) => { ws.onerror = (e) => {
Utils.logError(`OctoListener websocket onerror. data: ${e}`) Utils.logError(`OctoListener websocket onerror. data: ${e}`)
}; }
ws.onclose = (e) => { ws.onclose = (e) => {
Utils.log(`OctoListener websocket onclose, code: ${e.code}, reason: ${e.reason}`) Utils.log(`OctoListener websocket onclose, code: ${e.code}, reason: ${e.reason}`)
@ -64,7 +64,7 @@ class OctoListener {
timeoutId = undefined timeoutId = undefined
onChange(message.blockId) onChange(message.blockId)
}, this.notificationDelay) }, this.notificationDelay)
break; break
default: default:
Utils.logError(`Unexpected action: ${message.action}`) Utils.logError(`Unexpected action: ${message.action}`)
} }

View File

@ -1,20 +1,20 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
import {Block} from './blocks/block'; import {Block} from './blocks/block'
import {Board, IPropertyTemplate} from './blocks/board'; import {Board, IPropertyTemplate} from './blocks/board'
import {BoardView, ISortOption} from './blocks/boardView'; import {BoardView, ISortOption} from './blocks/boardView'
import {Card} from './blocks/card'; import {Card} from './blocks/card'
import {CommentBlock} from './blocks/commentBlock'; import {CommentBlock} from './blocks/commentBlock'
import {ImageBlock} from './blocks/imageBlock'; import {ImageBlock} from './blocks/imageBlock'
import {TextBlock} from './blocks/textBlock'; import {TextBlock} from './blocks/textBlock'
import {BoardTree} from './boardTree'; import {BoardTree} from './boardTree'
import {Editable} from './components/editable'; import {Editable} from './components/editable'
import {Menu} from './menu'; import {Menu} from './menu'
import mutator from './mutator'; import mutator from './mutator'
import {IBlock, IOrderedBlock} from './octoTypes'; import {IBlock, IOrderedBlock} from './octoTypes'
import {Utils} from './utils'; import {Utils} from './utils'
class OctoUtils { class OctoUtils {
static propertyDisplayValue(block: IBlock, propertyValue: string | undefined, propertyTemplate: IPropertyTemplate) { static propertyDisplayValue(block: IBlock, propertyValue: string | undefined, propertyTemplate: IPropertyTemplate) {
@ -22,10 +22,10 @@ class OctoUtils {
switch (propertyTemplate.type) { switch (propertyTemplate.type) {
case 'createdTime': case 'createdTime':
displayValue = Utils.displayDateTime(new Date(block.createAt)) displayValue = Utils.displayDateTime(new Date(block.createAt))
break; break
case 'updatedTime': case 'updatedTime':
displayValue = Utils.displayDateTime(new Date(block.updateAt)) displayValue = Utils.displayDateTime(new Date(block.updateAt))
break; break
default: default:
displayValue = propertyValue displayValue = propertyValue
} }
@ -57,9 +57,9 @@ class OctoUtils {
let element: JSX.Element let element: JSX.Element
if (propertyTemplate.type === 'select') { if (propertyTemplate.type === 'select') {
let className = 'octo-button octo-propertyvalue'; let className = 'octo-button octo-propertyvalue'
if (!displayValue) { if (!displayValue) {
className += ' empty'; className += ' empty'
} }
const showMenu = (clickedElement: HTMLElement) => { const showMenu = (clickedElement: HTMLElement) => {
@ -72,9 +72,9 @@ class OctoUtils {
menu.options.push(...propertyTemplate.options.map((o) => ({id: o.value, name: o.value}))) menu.options.push(...propertyTemplate.options.map((o) => ({id: o.value, name: o.value})))
menu.onMenuClicked = (optionId) => { menu.onMenuClicked = (optionId) => {
mutator.changePropertyValue(card, propertyTemplate.id, optionId) mutator.changePropertyValue(card, propertyTemplate.id, optionId)
}; }
menu.showAtElement(clickedElement) menu.showAtElement(clickedElement)
}; }
element = (<div element = (<div
key={propertyTemplate.id} key={propertyTemplate.id}
@ -151,7 +151,7 @@ class OctoUtils {
name: o.name, name: o.name,
icon: (sortOption?.propertyId === o.id) ? sortOption.reversed ? 'sortUp' : 'sortDown' : undefined, icon: (sortOption?.propertyId === o.id) ? sortOption.reversed ? 'sortUp' : 'sortDown' : undefined,
} }
}); })
Menu.shared.onMenuClicked = async (propertyId: string) => { Menu.shared.onMenuClicked = async (propertyId: string) => {
let newSortOptions: ISortOption[] = [] let newSortOptions: ISortOption[] = []
if (sortOption && sortOption.propertyId === propertyId) { if (sortOption && sortOption.propertyId === propertyId) {
@ -166,7 +166,7 @@ class OctoUtils {
} }
await mutator.changeViewSortOptions(activeView, newSortOptions) await mutator.changeViewSortOptions(activeView, newSortOptions)
}; }
Menu.shared.showAtElement(e.target as HTMLElement) Menu.shared.showAtElement(e.target as HTMLElement)
} }

View File

@ -1,18 +1,18 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom'
import {BoardView} from '../blocks/boardView'; import {BoardView} from '../blocks/boardView'
import {BoardTree} from '../boardTree'; import {BoardTree} from '../boardTree'
import {CardTree} from '../cardTree'; import {CardTree} from '../cardTree'
import {FilterComponent} from '../components/filterComponent'; import {FilterComponent} from '../components/filterComponent'
import {WorkspaceComponent} from '../components/workspaceComponent'; import {WorkspaceComponent} from '../components/workspaceComponent'
import {FlashMessage} from '../flashMessage'; import {FlashMessage} from '../flashMessage'
import mutator from '../mutator'; import mutator from '../mutator'
import {OctoListener} from '../octoListener'; import {OctoListener} from '../octoListener'
import {Utils} from '../utils'; import {Utils} from '../utils'
import {WorkspaceTree} from '../workspaceTree'; import {WorkspaceTree} from '../workspaceTree'
type Props = { type Props = {
} }
@ -166,7 +166,7 @@ export default class BoardPage extends React.Component<Props, State> {
this.boardListener.open(boardId, (blockId: string) => { this.boardListener.open(boardId, (blockId: string) => {
console.log(`octoListener.onChanged: ${blockId}`) console.log(`octoListener.onChanged: ${blockId}`)
this.sync(boardId) this.sync(boardId)
}); })
this.sync(boardId, viewId) this.sync(boardId, viewId)
} }

View File

@ -1,13 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
import {Archiver} from '../archiver'; import {Archiver} from '../archiver'
import {Board} from '../blocks/board'; import {Board} from '../blocks/board'
import Button from '../components/button'; import Button from '../components/button'
import octoClient from '../octoClient'; import octoClient from '../octoClient'
import {IBlock} from '../octoTypes'; import {IBlock} from '../octoTypes'
import {Utils} from '../utils'; import {Utils} from '../utils'
type Props = {} type Props = {}
@ -35,7 +35,7 @@ export default class HomePage extends React.Component<Props, State> {
importClicked = async () => { importClicked = async () => {
Archiver.importFullArchive(() => { Archiver.importFullArchive(() => {
this.loadBoards() this.loadBoards()
}); })
} }
exportClicked = async () => { exportClicked = async () => {

View File

@ -1,10 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
import Button from '../components/button'; import Button from '../components/button'
import './loginPage.scss'; import './loginPage.scss'
type Props = {} type Props = {}

View File

@ -1,8 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {IPropertyTemplate, PropertyType} from './blocks/board'; import {IPropertyTemplate, PropertyType} from './blocks/board'
import {Menu} from './menu'; import {Menu} from './menu'
import {Utils} from './utils'; import {Utils} from './utils'
class PropertyMenu extends Menu { class PropertyMenu extends Menu {
static shared = new PropertyMenu() static shared = new PropertyMenu()
@ -31,12 +31,12 @@ class PropertyMenu extends Menu {
const nameTextbox = ul.appendChild(Utils.htmlToElement('<li class="menu-textbox"></li>')) const nameTextbox = ul.appendChild(Utils.htmlToElement('<li class="menu-textbox"></li>'))
this.nameTextbox = nameTextbox this.nameTextbox = nameTextbox
let propertyValue = this.property ? this.property.name : ''; let propertyValue = this.property ? this.property.name : ''
nameTextbox.innerText = propertyValue nameTextbox.innerText = propertyValue
nameTextbox.contentEditable = 'true'; nameTextbox.contentEditable = 'true'
nameTextbox.onclick = (e) => { nameTextbox.onclick = (e) => {
e.stopPropagation() e.stopPropagation()
}; }
nameTextbox.onblur = () => { nameTextbox.onblur = () => {
if (nameTextbox.innerText !== propertyValue) { if (nameTextbox.innerText !== propertyValue) {
propertyValue = nameTextbox.innerText propertyValue = nameTextbox.innerText
@ -47,7 +47,7 @@ class PropertyMenu extends Menu {
} }
nameTextbox.onmouseenter = () => { nameTextbox.onmouseenter = () => {
this.hideSubMenu() this.hideSubMenu()
}; }
nameTextbox.onkeydown = (e) => { nameTextbox.onkeydown = (e) => {
if (e.keyCode === 13 || e.keyCode === 27) { if (e.keyCode === 13 || e.keyCode === 27) {
nameTextbox.blur(); e.stopPropagation() nameTextbox.blur(); e.stopPropagation()
@ -76,20 +76,20 @@ class PropertyMenu extends Menu {
private typeDisplayName(type: PropertyType): string { private typeDisplayName(type: PropertyType): string {
switch (type) { switch (type) {
case 'text': return 'Text'; case 'text': return 'Text'
case 'number': return 'Number'; case 'number': return 'Number'
case 'select': return 'Select'; case 'select': return 'Select'
case 'multiSelect': return 'Multi Select'; case 'multiSelect': return 'Multi Select'
case 'person': return 'Person'; case 'person': return 'Person'
case 'file': return 'File or Media'; case 'file': return 'File or Media'
case 'checkbox': return 'Checkbox'; case 'checkbox': return 'Checkbox'
case 'url': return 'URL'; case 'url': return 'URL'
case 'email': return 'Email'; case 'email': return 'Email'
case 'phone': return 'Phone'; case 'phone': return 'Phone'
case 'createdTime': return 'Created Time'; case 'createdTime': return 'Created Time'
case 'createdBy': return 'Created By'; case 'createdBy': return 'Created By'
case 'updatedTime': return 'Updated Time'; case 'updatedTime': return 'Updated Time'
case 'updatedBy': return 'Updated By'; case 'updatedBy': return 'Updated By'
} }
Utils.assertFailure(`typeDisplayName, unhandled type: ${type}`) Utils.assertFailure(`typeDisplayName, unhandled type: ${type}`)
} }

View File

@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import marked from 'marked'; import marked from 'marked'
declare global { declare global {
interface Window { interface Window {
@ -122,10 +122,10 @@ class Utils {
static setFavicon(icon?: string) { static setFavicon(icon?: string) {
const href = icon ? const href = icon ?
`data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90">${icon}</text></svg>` : `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90">${icon}</text></svg>` :
''; ''
const link = (document.querySelector("link[rel*='icon']") || document.createElement('link')) as HTMLLinkElement const link = (document.querySelector("link[rel*='icon']") || document.createElement('link')) as HTMLLinkElement
link.type = 'image/x-icon'; link.type = 'image/x-icon'
link.rel = 'shortcut icon'; link.rel = 'shortcut icon'
link.href = href link.href = href
document.getElementsByTagName('head')[0].appendChild(link) document.getElementsByTagName('head')[0].appendChild(link)
} }
@ -138,7 +138,7 @@ class Utils {
const illegalCharacters = ['\\', '/', '?', ':', '<', '>', '*', '|', '"', '.'] const illegalCharacters = ['\\', '/', '?', ':', '<', '>', '*', '|', '"', '.']
illegalCharacters.forEach((character) => { illegalCharacters.forEach((character) => {
sanitizedFilename = sanitizedFilename.replace(character, '') sanitizedFilename = sanitizedFilename.replace(character, '')
}); })
return sanitizedFilename return sanitizedFilename
} }
@ -146,14 +146,14 @@ class Utils {
static selectLocalFile(onSelect?: (file: File) => void, accept = '.jpg,.jpeg,.png'): void { static selectLocalFile(onSelect?: (file: File) => void, accept = '.jpg,.jpeg,.png'): void {
const input = document.createElement('input') const input = document.createElement('input')
input.type = 'file'; input.type = 'file'
input.accept = accept input.accept = accept
input.onchange = async () => { input.onchange = async () => {
const file = input.files![0] const file = input.files![0]
onSelect?.(file) onSelect?.(file)
}; }
input.style.display = 'none'; input.style.display = 'none'
document.body.appendChild(input) document.body.appendChild(input)
input.click() input.click()

View File

@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
type MenuOptionProps = { type MenuOptionProps = {
id: string, id: string,

View File

@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react'
type Props = { type Props = {
children?: React.ReactNode; children?: React.ReactNode;

View File

@ -1,9 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {Block} from './blocks/block'; import {Block} from './blocks/block'
import {Board} from './blocks/board'; import {Board} from './blocks/board'
import octoClient from './octoClient'; import octoClient from './octoClient'
import {OctoUtils} from './octoUtils'; import {OctoUtils} from './octoUtils'
class WorkspaceTree { class WorkspaceTree {
boards: Board[] = [] boards: Board[] = []