Commit efc9d198 authored by Alexandre Delanoë's avatar Alexandre Delanoë

Merge branch 'feature/annotation' of...

Merge branch 'feature/annotation' of ssh://gitlab.iscpif.fr:20022/gargantext/purescript-gargantext into dev-annotation
parents c5536a08 1ebbcae1
{
"presets": ["es2015"]
sourceMaps: "inline",
presets: [
["@babel/preset-env", {"useBuiltIns": "usage", "corejs": 3}],
"@babel/preset-react"
],
plugins: []
}
......@@ -8,3 +8,11 @@
/.purs*
/.psa*
/.spago
/dist/*
!/dist/styles/
!/dist/examples/
!/dist/fonts/
!/dist/images/
!/dist/js/
/dist/styles/*map
\ No newline at end of file
This diff is collapsed.
......@@ -8,32 +8,77 @@ processing, text-mining, complex networks analysis and interactive data
visualization to pave the way toward new kinds of interactions with your
digital corpora.
This software is a free software, developed by the CNRS Complex Systems
You will not find this software very useful without also running or being
granted access to a [backend](https://gitlab.iscpif.fr/gargantext/haskell-gargantext).
This software is free software, developed by the CNRS Complex Systems
Institute of Paris Île-de-France (ISC-PIF) and its partners.
## Installation of this library
## Development
### Installing dependencies
Before building gargantext, you must install the dependencies. We use
[yarn](https://yarnpkg.com/en/) for this. They have excellent
[installation instructions](https://yarnpkg.com/en/docs/install).
### Dependencies warning
This library purescript-gargantext is the Front End part of Gargantext.
you need the backend (haskell-gargantext) installation too.
Once you have yarn installed, you may install everything else simply:
### Installation steps
```shell
yarn install && yarn install-ps
```
1. Add `node_modules/.bin` to your path
2. Execute `./build`
You may now build:
In one command:
```shell
yarn build
```
```PATH="$PWD/node_modules/.bin:$PATH" ./build```
And run a repl:
```shell
yarn repl
```
## Note to the contributors
Please follow CONTRIBUTING.md
## Introduction
### How do I?
#### Add a javascript dependency?
Add it to `package.json`, under `dependencies` if it is needed at
runtime or `devDependencies` if it is not.
#### Add a purescript dependency?
Add it to `psc-package.json` without the `purescript-` prefix.
If is not in the package set, you will need to read the next section.
#### Add a custom or override package to the local package set?
You need to add an entry to the relevant map in
`packages.dhall`. There are comments in the file explaining how it
works. It's written in dhall, so you can use comments and such.
You will then need to rebuild the package set:
```shell
yarn rebuild-set
```
#### Upgrade the base package set local is based on to latest?
```shell
yarn rebase-set && yarn rebuild-set
```
## Theory Introduction
Making sense of out text isn't actually that hard, but it does require
a little background knowledge to understand.x
a little background knowledge to understand.
### N-grams
......@@ -61,6 +106,8 @@ N-grams are matched case insensitively and across whole words. Examples:
You may read more about n-grams [on wikipedia](https://en.wikipedia.org/wiki/N-gram).
<!-- TODO: Discuss punctuation -->
Gargantext allows you to define n-grams interactively in your browser
and explore the relationships they uncover across a corpus of text.
......@@ -83,7 +130,7 @@ unigram/1-gram
: A one-word n-gram, e.g. `cow`, `coffee`
bigram/2-gram
: A two-word n-gram, e.g. `coffee cup`
trigram
trigram/3-gram
: A three-word n-gram, e.g. `coffee cup holder`
<!-- skip-grams are not yet supported -->
<!-- skip-gram -->
......
> 0.25%
not dead
\ No newline at end of file
......@@ -5,11 +5,11 @@
<title>CNRS GarganText</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://use.fontawesome.com/releases/v5.0.8/css/all.css" rel="stylesheet">
<link href="css/login.min.css" rel="stylesheet">
<link href="css/bootstrap.min.css" rel="stylesheet">
<link href="styles/login.min.css" rel="stylesheet">
<link href="styles/bootstrap.min.css" rel="stylesheet">
<!-- <link href="css/lavish-bootstrap.css" rel="stylesheet"> -->
<link rel="stylesheet" type="text/css" href="css/menu.css"/>
<link href="css/Login.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="styles/menu.css"/>
<link href="styles/Login.css" rel="stylesheet">
<style>
* {margin: 0; padding: 0; list-style: none;}
.tree ul li {
......
This diff is collapsed.
{
"scripts": {
"rebase-set": "spago package-set-upgrade && spago psc-package insdhall",
"rebuild-set": "spago psc-package-insdhall",
"install-ps": "psc-package install",
"build": "pulp --psc-package browserify -t dist/bundle.js",
"repl": "pulp --psc-package repl"
},
"dependencies": {
"@babel/polyfill": "^7.0.0",
"core-js": "^3",
"create-react-class": "^15.6.3",
"echarts": "^4.1.0",
"echarts-for-react": "^2.0.14",
"imports-loader": "^0.8.0",
"prop-types": "15.6.2",
"react": "^16.4.2",
"react-dom": "^16.4.2",
"react": "^16.8.2",
"react-dom": "^16.8.2",
"react-sigma": "git://github.com/np/react-sigma.git#shouldComponentUpdate"
},
"browserify": {
"transform": [
[
"babelify",
{
"presets": [
"es2015",
"stage-0",
"react"
]
}
]
]
},
"babel": {
"presets": [
"es2015",
"stage-0",
"react"
]
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"babel-runtime": "^6.26.0",
"babelify": "^8.0.0",
"bower": "^1.8.4",
"@babel/cli": "^7.1.5",
"@babel/core": "^7.1.6",
"@babel/preset-env": "^7.1.6",
"@babel/preset-react": "^7.0.0",
"@babel/preset-stage-2": "^7.0.0",
"babel-core": "^7.0.0-bridge",
"react-testing-library": "^6.1.2",
"envify": "^4.1.0",
"http-server": "^0.11.1",
"psc-package": "^0.3.2",
"pulp": "^12.3.0",
"purescript": "^0.12.0"
"psc-package": "^3.0.1",
"pulp": "^12.4.0",
"purescript": "^0.12.4",
"spago": "^0.7.5"
},
"version": "0.0.0"
}
......@@ -109,39 +109,67 @@ let additions =
-}
let mkPackage =
https://raw.githubusercontent.com/spacchetti/spacchetti/20181209/src/mkPackage.dhall sha256:8e1c6636f8a089f972b21cde0cef4b33fa36a2e503ad4c77928aabf92d2d4ec9
https://raw.githubusercontent.com/purescript/package-sets/psc-0.12.3-20190409/src/mkPackage.dhall sha256:0b197efa1d397ace6eb46b243ff2d73a3da5638d8d0ac8473e8e4a8fc528cf57
let upstream =
https://raw.githubusercontent.com/spacchetti/spacchetti/20181209/src/packages.dhall sha256:c63285af67ae74feb2f6eb67521712441928d2726ea10e2040774849ca765027
https://raw.githubusercontent.com/purescript/package-sets/psc-0.12.3-20190409/src/packages.dhall sha256:d327eb707262c9087f8d5caf471690bf69afc8f6307618d9c2ceab13755fb194
let overrides =
{ thermite =
mkPackage
[ "aff", "coroutines", "web-dom", "freet", "profunctor-lenses", "react", "react-dom" ]
"https://github.com/np/purescript-thermite.git"
"hide" }
let additions =
{ sequences =
mkPackage
[ "prelude", "unsafe-coerce", "partial", "unfoldable", "lazy", "arrays", "profunctor", "maybe", "tuples", "newtype" ]
"https://github.com/hdgarrood/purescript-sequences.git"
"v2.1.0"
, spec-discovery =
mkPackage
[ "prelude", "effect", "arrays", "spec", "node-fs" ]
"https://github.com/purescript-spec/purescript-spec-discovery"
"v3.1.0"
, spec-quickcheck =
mkPackage
[ "prelude", "aff", "random", "quickcheck", "spec" ]
"https://github.com/purescript-spec/purescript-spec-quickcheck"
"v3.1.0"
, uint =
mkPackage
[ "maybe", "math", "generics-rep" ]
"https://github.com/zaquest/purescript-uint"
"v5.1.1"
}
{ thermite =
mkPackage
[ "aff"
, "coroutines"
, "web-dom"
, "freet"
, "profunctor-lenses"
, "react"
, "react-dom"
]
"https://github.com/np/purescript-thermite.git"
"hide"
}
in upstream ⫽ overrides ⫽ additions
let additions =
{ sequences =
mkPackage
[ "prelude"
, "unsafe-coerce"
, "partial"
, "unfoldable"
, "lazy"
, "arrays"
, "profunctor"
, "maybe"
, "tuples"
, "newtype"
]
"https://github.com/hdgarrood/purescript-sequences.git"
"v2.1.0"
, spec-discovery =
mkPackage
[ "prelude", "effect", "arrays", "spec", "node-fs" ]
"https://github.com/purescript-spec/purescript-spec-discovery"
"v3.1.0"
, spec-quickcheck =
mkPackage
[ "prelude", "aff", "random", "quickcheck", "spec" ]
"https://github.com/purescript-spec/purescript-spec-quickcheck"
"v3.1.0"
, dom-simple =
mkPackage
[ "arrays", "console", "effect", "functions", "nullable", "prelude", "unsafe-coerce" ]
"https://github.com/irresponsible/purescript-dom-simple"
"master"
, reactix =
mkPackage
[ "console", "dom-simple", "effect", "functions", "nullable", "prelude", "unsafe-coerce" ]
"https://github.com/irresponsible/purescript-reactix"
"master"
, uint =
mkPackage
[ "maybe", "math", "generics-rep" ]
"https://github.com/zaquest/purescript-uint"
"v5.1.1"
}
in upstream // overrides // additions
{
"name": "gargantext",
"set": "master",
"source": "https://github.com/np/package-sets.git",
"set": "local",
"source": ".psc-package",
"depends": [
"spec-quickcheck",
"spec-discovery",
......@@ -22,6 +22,8 @@
"console",
"strings",
"string-parsers",
"prelude"
"prelude",
"dom-simple",
"reactix"
]
}
{ name = "gargantext"
, dependencies =
[ "affjax"
, "argonaut"
, "console"
, "css"
, "effect"
, "foldable-traversable"
, "foreign-object"
, "generics-rep"
, "integers"
, "js-timers"
, "prelude"
, "psci-support"
, "random"
, "routing"
, "sequences"
, "spec"
, "spec-quickcheck"
, "spec-discovery"
, "strings"
, "thermite"
, "uint"
, "unicode"
, "web-html" ]
, packages = ./packages.dhall }
......@@ -12,123 +12,79 @@
module Gargantext.Components.Annotated.AnnotatedField where
import Prelude hiding (div)
import Data.Unit (Unit, unit)
import Data.Array (fromFoldable)
import Data.Array as A
import Data.Map as Map
import Data.Maybe (Maybe(..), maybe)
import Data.Lens (Lens', Prism', over, view, lens)
import Data.Maybe (Maybe(..), maybe, isJust)
import Data.Lens (Lens', lens)
import Data.List as List
import Data.List (List(..), mapWithIndex, toUnfoldable, sortBy)
import Data.Ordering (Ordering(..))
import Data.List (List(..))
import Data.Tuple (Tuple(..))
import Effect (Effect)
import Effect.Class.Console (log)
import Effect.Unsafe (unsafePerformEffect)
import React (Children, ReactElement, ReactClass, createElement)
import React.DOM (a, div, p, span, nav, text)
import React.DOM.Props (className, onContextMenu)
import Thermite ( PerformAction, Render, Spec
, defaultPerformAction, createClass, createReactSpec
, _render, modifyState, writeState, focus, focusState
, simpleSpec, withState)
import React (ReactElement, ReactClass, createElement)
import Gargantext.Types (TermList(..))
import Gargantext.Components.NgramsTable (NgramsTable(..), highlightNgrams, termStyle)
import Gargantext.Utils.React (WithChildren)
import Gargantext.Components.NgramsTable (NgramsTable(..), highlightNgrams)
import Gargantext.Utils.Selection (getSelection, toString)
import React as React
import React.SyntheticEvent (SyntheticMouseEvent, pageX, pageY)
import Reactix as R
import Reactix.DOM.Raw as RDOM
newtype PageOffset = PageOffset { x :: Number, y :: Number }
type Run = Tuple String (Maybe TermList)
type State = { runs :: List Run, contextMenu :: { visible :: Boolean } }
type Props = ( ngrams :: NgramsTable, text :: Maybe String )
type Props' = ( ngrams :: NgramsTable, text :: Maybe String )
type Props = { | Props' }
defaultProps :: Record Props
defaultProps = { ngrams: NgramsTable Map.empty, text: Nothing }
data Action
= ForceRefresh
| OnContextMenu PageOffset String
| AddTerm String TermList
annotatedField :: Record Props -> R.Element
annotatedField = R.createLeaf annotatedFieldComponent
defaultProps :: Props
defaultProps = { ngrams: NgramsTable Map.empty, text: Nothing }
defaultState :: State
defaultState = { runs: Nil, contextMenu: { visible: false } } -- contextMenu: ContextMenu.defaultState }
annotatedField :: Props -> ReactElement
annotatedField p = createElement annotatedFieldClass p []
annotatedFieldClass :: ReactClass (WithChildren Props')
annotatedFieldClass =
React.component "AnnotatedField"
(\this -> do
-- log $ "AnnotatedField: constructing"
s <- spec this
pure { state : s.state
, render: s.render
, componentDidUpdate: \old _s _snap -> do
new <- React.getProps this
when (old.ngrams /= new.ngrams || old.text /= new.text) do
-- log "AnnotatedField: forcing refresh"
dispatcher this ForceRefresh
})
annotatedFieldComponent :: R.Component Props
annotatedFieldComponent = R.pureLeaf "AnnotatedField" cpt
where
performAction :: PerformAction State Props Action
performAction ForceRefresh = forceRefresh
performAction _ = \_ _ -> pure unit
-- performAction (ShowContextMenu i) = showContextMenu i
-- performAction (AddTerm t l) = addTerm t l
-- performAction = defaultPerformAction
render :: Render State Props Action
render d _p s _c = [ p [className "annotated-field"] $ children d s.runs ]
children d = fromFoldable <<< map (renderRun $ contextMenuHandler d)
renderRun menu (Tuple txt lst)
| Just list <- lst = span [termStyle list, onContextMenu menu] [text txt]
| otherwise = span [] [text txt]
{spec, dispatcher} = createReactSpec (simpleSpec performAction render) compile
-- performAction handlers
forceRefresh props state =
do wrote <- writeState (compile props)
log $ msg wrote
pure unit
where msg = maybe "AnnotatedField: failed to write new state" (const "AnnotatedField: wrote new state")
-- showContextMenu :: PerformAction State Props String
-- showContextMenu p s = pure unit
-- addTerm :: String -> PerformAction State Props TermList
-- addTerm t l p s = pure unit
compile :: Props -> State
compile props =
unsafePerformEffect $
do let ret = { runs: runs props.text, contextMenu: { visible: false } }
log "Compiling..."
pure ret
where runs (Just txt) = highlight props.ngrams txt
runs _ = Nil
cpt props = R.createDOMElement "p" { className: "annotated-field" } $ children props
children props = A.fromFoldable (annotateRun <$> compile props)
type RunProps = ( list :: Maybe TermList, text :: String )
-- highlightNgrams :: NgramsTable -> String -> Array (Tuple String (Maybe TermList))
annotateRun :: Run -> R.Element
annotateRun (Tuple text list) = R.createLeaf annotatedRunComponent { text, list }
annotatedRunComponent :: R.Component RunProps
annotatedRunComponent = R.pureLeaf "AnnotatedRun" cpt
where cpt { text, list } = maybe (unstyled text) (styled text) list
styled text list = R.createDOMElement "span" { style: termStyle list } [ RDOM.text text ]
unstyled text = R.createDOMElement "span" {} [ RDOM.text text ]
compile :: Record Props -> List Run
compile props = runs props.text
where runs (Just text) = highlight props.ngrams text
runs _ = Nil
highlight :: NgramsTable -> String -> List Run
highlight n t = List.fromFoldable $ highlightNgrams n t
contextMenuHandler :: (Action -> Effect Unit) -> SyntheticMouseEvent -> Effect Unit
contextMenuHandler d e =
do sel <- getSelection
case toString <$> sel of
Just s -> submit s
Nothing -> pure unit
where submit s = offset >>= \o -> d $ OnContextMenu o s
offset =
do x <- pageX e
y <- pageY e
pure $ PageOffset { x, y }
_runs = lens (\a -> a.runs) (\a r -> a { runs = r })
_contextMenu = lens (\a -> a.contextMenu) (\a m -> a { contextMenu = m })
-- contextMenuHandler :: (Action -> Effect Unit) -> SyntheticMouseEvent -> Effect Unit
-- contextMenuHandler d e =
-- do sel <- getSelection
-- case toString <$> sel of
-- Just s -> submit s
-- Nothing -> pure unit
-- where submit s = offset >>= \o -> d $ OnContextMenu o s
-- offset =
-- do x <- pageX e
-- y <- pageY e
-- pure $ PageOffset { x, y }
termStyle :: TermList -> { backgroundColor :: String }
termStyle GraphTerm = { backgroundColor: "green" }
termStyle StopTerm = { backgroundColor: "red" }
termStyle CandidateTerm = { backgroundColor: "black" }
-- _runs :: Lens' State (List Run)
-- _runs = lens (\a -> a.runs) (\a r -> a { runs = r })
-- _contextMenu :: Lens' State ???
-- _contextMenu = lens (\a -> a.contextMenu) (\a m -> a { contextMenu = m })
module Gargantext.Pages.Corpus.Document where
import Data.Argonaut (class DecodeJson, class EncodeJson, decodeJson, jsonEmptyObject, (.?), (.??), (:=), (~>))
import Data.Argonaut (class DecodeJson, decodeJson, (.:), (.:?))
import Data.Generic.Rep (class Generic)
import Data.Lens (Lens', lens, (?~))
import Data.Generic.Rep.Show (genericShow)
......@@ -9,11 +9,10 @@ import Data.Map as Map
import Data.Set as Set
import Data.Tuple (Tuple(..))
import Data.Maybe (Maybe(..), maybe)
import Data.Either (Either(..))
import Effect.Aff (Aff)
import React (ReactElement)
import React.DOM (a, button, div, h4, h6, input, li, nav, option, p, select, span, text, ul)
import React.DOM.Props (_data, _id, _type, aria, className, href, name, onChange, onInput, placeholder, role, style, value)
import React.DOM (div, h4, li, option, p, span, text, ul)
import React.DOM.Props (className, value)
import Thermite (PerformAction, Render, Spec, modifyState, simpleSpec)
import Unsafe.Coerce (unsafeCoerce)
import Control.Monad.Trans.Class (lift)
......@@ -25,6 +24,7 @@ import Gargantext.Components.Node (NodePoly(..))
import Gargantext.Components.NgramsTable (NgramsTable(..), NgramsElement(..))
import Gargantext.Components.Annotated.AnnotatedField as AnnotatedField
import Gargantext.Types (TermList(..))
import Gargantext.Utils.React ( crapify )
nge :: String -> Tuple String NgramsElement
nge word = Tuple word elem where
......@@ -39,7 +39,6 @@ testTable = NgramsTable $ Map.fromFoldable $ nge <$> words
type State =
{ document :: Maybe (NodePoly Document)
, annotatedDocument :: AnnotatedDocument
, ngramsTable :: NgramsTable
, inputValue :: String
}
......@@ -47,7 +46,6 @@ type State =
initialState :: {} -> State
initialState {} =
{ document: Nothing
, annotatedDocument: defaultAnnotatedDocument
, ngramsTable: testTable
, inputValue: ""
}
......@@ -137,13 +135,6 @@ data Document
--, text :: Maybe String
}
data AnnotatedDocument
= AnnotatedDocument
{ abstract :: AnnotatedField.State }
defaultAnnotatedDocument :: AnnotatedDocument
defaultAnnotatedDocument = AnnotatedDocument { abstract: AnnotatedField.defaultState }
defaultNodeDocument :: NodePoly Document
defaultNodeDocument =
NodePoly { id : 0
......@@ -196,9 +187,9 @@ instance decodeStatus :: DecodeJson Status
where
decodeJson json = do
obj <- decodeJson json
failed <- obj .? "failed"
succeeded <- obj .? "succeeded"
remaining <- obj .? "remaining"
failed <- obj .: "failed"
succeeded <- obj .: "succeeded"
remaining <- obj .: "remaining"
pure $ Status {failed, succeeded, remaining}
......@@ -206,23 +197,23 @@ instance decodeDocumentV3 :: DecodeJson DocumentV3
where
decodeJson json = do
obj <- decodeJson json
abstract <- obj .?? "abstract"
authors <- obj .? "authors"
--error <- obj .? "error"
language_iso2 <- obj .? "language_iso2"
language_iso3 <- obj .? "language_iso3"
language_name <- obj .? "language_name"
publication_date <- obj .? "publication_date"
publication_day <- obj .? "publication_day"
publication_hour <- obj .? "publication_hour"
publication_minute <- obj .? "publication_minute"
publication_month <- obj .? "publication_month"
publication_second <- obj .? "publication_second"
publication_year <- obj .? "publication_year"
realdate_full_ <- obj .? "realdate_full_"
source <- obj .? "source"
statuses <- obj .? "statuses"
title <- obj .? "title"
abstract <- obj .:? "abstract"
authors <- obj .: "authors"
--error <- obj .: "error"
language_iso2 <- obj .: "language_iso2"
language_iso3 <- obj .: "language_iso3"
language_name <- obj .: "language_name"
publication_date <- obj .: "publication_date"
publication_day <- obj .: "publication_day"
publication_hour <- obj .: "publication_hour"
publication_minute <- obj .: "publication_minute"
publication_month <- obj .: "publication_month"
publication_second <- obj .: "publication_second"
publication_year <- obj .: "publication_year"
realdate_full_ <- obj .: "realdate_full_"
source <- obj .: "source"
statuses <- obj .: "statuses"
title <- obj .: "title"
pure $ DocumentV3 { abstract
, authors
--, error
......@@ -246,25 +237,25 @@ instance decodeDocument :: DecodeJson Document
where
decodeJson json = do
obj <- decodeJson json
abstract <- obj .?? "abstract"
authors <- obj .?? "authors"
bdd <- obj .?? "bdd"
doi <- obj .?? "doi"
language_iso2 <- obj .?? "language_iso2"
-- page <- obj .?? "page"
publication_date <- obj .?? "publication_date"
--publication_second <- obj .?? "publication_second"
--publication_minute <- obj .?? "publication_minute"
--publication_hour <- obj .?? "publication_hour"
publication_day <- obj .?? "publication_day"
publication_month <- obj .?? "publication_month"
publication_year <- obj .?? "publication_year"
source <- obj .?? "sources"
institutes <- obj .?? "institutes"
title <- obj .?? "title"
uniqId <- obj .?? "uniqId"
--url <- obj .? "url"
--text <- obj .? "text"
abstract <- obj .:? "abstract"
authors <- obj .:? "authors"
bdd <- obj .:? "bdd"
doi <- obj .:? "doi"
language_iso2 <- obj .:? "language_iso2"
-- page <- obj .:? "page"
publication_date <- obj .:? "publication_date"
--publication_second <- obj .:? "publication_second"
--publication_minute <- obj .:? "publication_minute"
--publication_hour <- obj .:? "publication_hour"
publication_day <- obj .:? "publication_day"
publication_month <- obj .:? "publication_month"
publication_year <- obj .:? "publication_year"
source <- obj .:? "sources"
institutes <- obj .:? "institutes"
title <- obj .:? "title"
uniqId <- obj .:? "uniqId"
--url <- obj .: "url"
--text <- obj .: "text"
pure $ Document { abstract
, authors
, bdd
......@@ -339,7 +330,7 @@ docview = simpleSpec performAction render
]
]
where
annotate t = AnnotatedField.annotatedField { ngrams: state.ngramsTable, text: t }
annotate t = crapify $ AnnotatedField.annotatedField { ngrams: state.ngramsTable, text: t }
li' = li [className "list-group-item justify-content-between"]
text' x = text $ maybe "Nothing" identity x
badge s = span [className "badge badge-default badge-pill"] [text s]
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment