You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-13 22:12:50 +02:00
Clean up all files
This commit is contained in:
@@ -1,3 +1,11 @@
|
||||
"C:\Qt\5.7\msvc2015\bin\qmake.exe" D:\Web\www\joplin\QtClient\JoplinQtClient\JoplinQtClient.pro -spec win32-msvc2015 "CONFIG+=debug" "CONFIG+=qml_debug"
|
||||
@echo off
|
||||
D:
|
||||
mkdir "D:\Web\www\joplin\QtClient\build-JoplinQtClient-Visual_C_32_bits-Debug\"
|
||||
cd "D:\Web\www\joplin\QtClient\build-JoplinQtClient-Visual_C_32_bits-Debug\"
|
||||
"C:\Qt\5.7\msvc2015\bin\qmake.exe" D:\Web\www\joplin\QtClient\JoplinQtClient\JoplinQtClient.pro -spec win32-msvc2015 "CONFIG+=debug" "CONFIG+=qml_debug" "JOP_FRONT_END_GUI=1"
|
||||
"C:\Qt\Tools\QtCreator\bin\jom.exe" qmake_all
|
||||
"C:\Qt\Tools\QtCreator\bin\jom.exe"
|
||||
|
||||
rem "C:\Qt\5.7\msvc2015\bin\qmake.exe" D:\Web\www\joplin\QtClient\JoplinQtClient\JoplinQtClient.pro -spec win32-msvc2015 "CONFIG+=debug" "CONFIG+=qml_debug"
|
||||
rem "C:\Qt\Tools\QtCreator\bin\jom.exe" qmake_all
|
||||
rem "C:\Qt\Tools\QtCreator\bin\jom.exe"
|
@@ -1,8 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
BUILD_DIR=/home/laurent/src/notes/QtClient/build-JoplinQtClient-Desktop_Qt_5_7_1_GCC_64bit-Debug
|
||||
mkdir -p "$BUILD_DIR"
|
||||
cd "$BUILD_DIR"
|
||||
/opt/Qt/5.7/gcc_64/bin/qmake /home/laurent/src/notes/QtClient/JoplinQtClient/JoplinQtClient.pro -spec linux-g++ CONFIG+=debug CONFIG+=qml_debug JOP_FRONT_END_CLI=1
|
||||
/usr/bin/make qmake_all
|
||||
/usr/bin/make
|
||||
set -e
|
||||
|
||||
mkdir -p /cygdrive/d/Web/www/joplin/QtClient/build-JoplinQtClient-Visual_C_32_bits-Debug
|
||||
cd /cygdrive/d/Web/www/joplin/QtClient/build-JoplinQtClient-Visual_C_32_bits-Debug
|
||||
rm -rf debug/ release/ Makefile*
|
||||
export PATH="/cygdrive/c/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin":$PATH
|
||||
export PATH=$PATH:"/cygdrive/c/Program Files (x86)/Windows Kits/8.1/bin/x86"
|
||||
export PATH=$PATH:"/cygdrive/c/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include"
|
||||
"/cygdrive/c/Qt/5.7/msvc2015/bin/qmake.exe" D:\\Web\\www\\joplin\\QtClient\\JoplinQtClient\\JoplinQtClient.pro -spec win32-msvc2015 "CONFIG+=debug" "CONFIG+=qml_debug" "JOP_FRONT_END_GUI=1"
|
||||
"/cygdrive/c/Qt/Tools/QtCreator/bin/jom.exe" qmake_all
|
||||
"/cygdrive/c/Qt/Tools/QtCreator/bin/jom.exe"
|
||||
rsync -a /cygdrive/d/Web/www/joplin/QtClient/dependencies/dll-debug/ /cygdrive/d/Web/www/joplin/QtClient/build-JoplinQtClient-Visual_C_32_bits-Debug/debug
|
||||
cd -
|
||||
|
||||
|
||||
|
||||
# BUILD_DIR=/home/laurent/src/notes/QtClient/build-JoplinQtClient-Desktop_Qt_5_7_1_GCC_64bit-Debug
|
||||
# mkdir -p "$BUILD_DIR"
|
||||
# cd "$BUILD_DIR"
|
||||
# /opt/Qt/5.7/gcc_64/bin/qmake /home/laurent/src/notes/QtClient/JoplinQtClient/JoplinQtClient.pro -spec linux-g++ CONFIG+=debug CONFIG+=qml_debug JOP_FRONT_END_CLI=1
|
||||
# /usr/bin/make qmake_all
|
||||
# /usr/bin/make
|
2
QtClient/JoplinQtClient/cliapplication.cpp
Normal file → Executable file
2
QtClient/JoplinQtClient/cliapplication.cpp
Normal file → Executable file
@@ -1,4 +1,4 @@
|
||||
#include <stab.h>
|
||||
#include <stable.h>
|
||||
|
||||
#include "cliapplication.h"
|
||||
#include "constants.h"
|
||||
|
@@ -2,7 +2,8 @@
|
||||
|
||||
set -e
|
||||
|
||||
cd /cygdrive/d/Web/www/joplin/QtClient/build-evernote-import-qt-Visual_C_32_bites-Debug
|
||||
mkdir -p /cygdrive/d/Web/www/joplin/QtClient/build-evernote-import-qt-Visual_C_32_bits-Debug
|
||||
cd /cygdrive/d/Web/www/joplin/QtClient/build-evernote-import-qt-Visual_C_32_bits-Debug
|
||||
rm -rf debug/ release/ Makefile*
|
||||
export PATH="/cygdrive/c/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin":$PATH
|
||||
export PATH=$PATH:"/cygdrive/c/Program Files (x86)/Windows Kits/8.1/bin/x86"
|
||||
@@ -10,5 +11,5 @@ export PATH=$PATH:"/cygdrive/c/Program Files (x86)/Microsoft Visual Studio 14.0/
|
||||
"/cygdrive/c/Qt/5.7/msvc2015/bin/qmake.exe" D:\\Web\\www\\joplin\\QtClient\\evernote-import\\evernote-import-qt.pro -spec win32-msvc2015 "CONFIG+=debug" "CONFIG+=qml_debug"
|
||||
"/cygdrive/c/Qt/Tools/QtCreator/bin/jom.exe" qmake_all
|
||||
"/cygdrive/c/Qt/Tools/QtCreator/bin/jom.exe"
|
||||
rsync -a /cygdrive/d/Web/www/joplin/QtClient/dependencies/dll-debug/ /cygdrive/d/Web/www/joplin/QtClient/build-evernote-import-qt-Visual_C_32_bites-Debug/debug
|
||||
rsync -a /cygdrive/d/Web/www/joplin/QtClient/dependencies/dll-debug/ /cygdrive/d/Web/www/joplin/QtClient/build-evernote-import-qt-Visual_C_32_bits-Debug/debug
|
||||
cd -
|
@@ -1 +0,0 @@
|
||||
{"last_sync_id":81,"file_map":[],"test":"abcd","client_id":"11111111111111111111111111111111","last_sync_time":1476289357,"session_id":null,"folder_items":[]}
|
@@ -1 +0,0 @@
|
||||
{"last_sync_id":80,"file_map":[],"client_id":"22222222222222222222222222222222","last_sync_time":1476289618,"folder_items":[],"session_id":null}
|
@@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
php main.php --config ~/src/notes/cli-client/.config1 "$@"
|
@@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
php main.php --config ~/src/notes/cli-client/.config2 "$@"
|
@@ -1,174 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
//"bufio"
|
||||
//"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
// "os/exec"
|
||||
// "os/user"
|
||||
"path/filepath"
|
||||
"path"
|
||||
"crypto/md5"
|
||||
// "runtime"
|
||||
// "strconv"
|
||||
"strings"
|
||||
// "time"
|
||||
"net/url"
|
||||
"net/http"
|
||||
|
||||
"github.com/jessevdk/go-flags"
|
||||
)
|
||||
|
||||
const VERSION = "1.0.0"
|
||||
|
||||
type SyncCommandOptions struct {
|
||||
// FfmpegPath string `long:"ffmpeg" description:"Path to ffmpeg." default:"ffmpeg"`
|
||||
// FrameDirPath string `short:"d" long:"frame-dir" description:"Path to directory that will contain the captured frames. (default: <PictureDirectory>/pmcctv)"`
|
||||
// RemoteDir string `short:"r" long:"remote-dir" description:"Remote location where frames will be saved to. Must contain a path compatible with scp (eg. user@someip:~/pmcctv)."`
|
||||
// RemotePort string `short:"p" long:"remote-port" description:"Port of remote location where frames will be saved to. If not set, whatever is the default scp port will be used (should be 22)."`
|
||||
// BurstModeDuration int `short:"b" long:"burst-mode-duration" description:"Duration of burst mode, in seconds. Set to 0 to disable burst mode altogether." default:"10"`
|
||||
// BurstModeFormat string `short:"f" long:"burst-mode-format" description:"Format of burst mode captured files, either \"image\" or \"video\"." default:"video"`
|
||||
// FramesTtl int `short:"t" long:"time-to-live" description:"For how long captured frames should be kept, in days." default:"7"`
|
||||
// InputDevice string `short:"i" long:"input-device" description:"Name of capture input device. (default: auto-detect)"`
|
||||
}
|
||||
|
||||
type AppCommandOptions struct {
|
||||
Version bool `long:"version" description:"Display version information"`
|
||||
}
|
||||
|
||||
type CommandOptions struct {
|
||||
App AppCommandOptions
|
||||
Sync SyncCommandOptions
|
||||
}
|
||||
|
||||
func printHelp(flagParser *flags.Parser) {
|
||||
flagParser.WriteHelp(os.Stdout)
|
||||
fmt.Printf("\n")
|
||||
fmt.Printf("For help with a particular command, type \"%s <command> --help\"\n", path.Base(os.Args[0]))
|
||||
}
|
||||
|
||||
func createFlagParser() (CommandOptions, *flags.Parser) {
|
||||
var opts CommandOptions
|
||||
flagParser := flags.NewParser(&opts.App, flags.HelpFlag|flags.PassDoubleDash)
|
||||
|
||||
flagParser.AddCommand(
|
||||
"sync",
|
||||
"Synchronize notes",
|
||||
"Synchronize local notes with the server.",
|
||||
&opts.Sync,
|
||||
)
|
||||
|
||||
return opts, flagParser
|
||||
}
|
||||
|
||||
func createId(path string) string {
|
||||
h := md5.New()
|
||||
io.WriteString(h, "31208854954776365651")
|
||||
io.WriteString(h, path)
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
func readFile(path string) (string, error) {
|
||||
content, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(content), nil
|
||||
}
|
||||
|
||||
func makeApiCall(method string, path string, data url.Values) (error, []byte) {
|
||||
baseUrl := "http://127.0.0.1:8000"
|
||||
fullUrl := baseUrl + "/" + path
|
||||
|
||||
client := http.Client{}
|
||||
request, err := http.NewRequest(method, fullUrl, strings.NewReader(data.Encode()))
|
||||
if err != nil {
|
||||
return err, []byte{}
|
||||
}
|
||||
|
||||
request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
response, err := client.Do(request)
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return err, []byte{}
|
||||
}
|
||||
|
||||
return nil, body
|
||||
}
|
||||
|
||||
// type Note struct {
|
||||
// Id string
|
||||
// Title string
|
||||
// Body string
|
||||
// }
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
|
||||
//err, body := makeApiCall("GET", "users/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", url.Values{})
|
||||
|
||||
note := url.Values{}
|
||||
note.Add("title", "from go")
|
||||
note.Add("body", "body from go")
|
||||
|
||||
err, body := makeApiCall("POST", "notes", note)
|
||||
fmt.Println(err)
|
||||
fmt.Println(string(body))
|
||||
os.Exit(0)
|
||||
|
||||
|
||||
|
||||
opts, flagParser := createFlagParser()
|
||||
|
||||
args, err := flagParser.Parse()
|
||||
|
||||
if err != nil {
|
||||
t := err.(*flags.Error).Type
|
||||
if t == flags.ErrHelp {
|
||||
printHelp(flagParser)
|
||||
os.Exit(0)
|
||||
} else if t == flags.ErrCommandRequired {
|
||||
// Here handle default flags (which are not associated with any command)
|
||||
if opts.App.Version {
|
||||
fmt.Println(VERSION)
|
||||
os.Exit(0)
|
||||
}
|
||||
printHelp(flagParser)
|
||||
os.Exit(0)
|
||||
} else {
|
||||
fmt.Printf("Error: %s\n", err)
|
||||
fmt.Printf("Type '%s --help' for more information.\n", path.Base(os.Args[0]))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
_ = args
|
||||
|
||||
fullPath := "/home/laurent/src/notes/cli-client/test"
|
||||
|
||||
walkPath := func (path string, info os.FileInfo, err error) error {
|
||||
if len(path) - len(fullPath) <= 0 {
|
||||
return nil
|
||||
}
|
||||
p := path[len(fullPath)+1:];
|
||||
fmt.Println(p)
|
||||
fmt.Println(createId(p))
|
||||
|
||||
if !info.IsDir() {
|
||||
content, err := readFile(path)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
fmt.Println(content)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
filepath.Walk(fullPath, walkPath)
|
||||
}
|
@@ -1,349 +0,0 @@
|
||||
<?php
|
||||
|
||||
function escapePathElement($element) {
|
||||
$valid = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ÀàÁáÂâÃãÄäÇçÈèÉéÊêËëÌìÍíÎîÏïÑnÒòÓóÔôÕõÖöŠšÚùÛúÜûÙüÝyŸÿŽz_- ().,';
|
||||
$output = '';
|
||||
$chars = preg_split('//u', $element, -1, PREG_SPLIT_NO_EMPTY); // Split a UTF-8 string into characters
|
||||
foreach ($chars as $c) {
|
||||
if (strpos($valid, $c) !== false) {
|
||||
$output .= $c;
|
||||
} else {
|
||||
$output .= rawurlencode($c);
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
function escapePath($path) {
|
||||
$output = '';
|
||||
$elements = preg_split('/[\\\\\/]/', $path);
|
||||
for ($i = 0; $i < count($elements); $i++) {
|
||||
$e = $elements[$i];
|
||||
if ($i > 0) $output .= '/';
|
||||
$output .= escapePathElement($e);
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
class Api {
|
||||
|
||||
private $sessionId = null;
|
||||
private $baseUrl = null;
|
||||
|
||||
public function __construct($baseUrl) {
|
||||
$this->baseUrl = $baseUrl;
|
||||
}
|
||||
|
||||
static public function createId($string) {
|
||||
// TODO: This needs to be unique per user
|
||||
return md5('gKcr0 ^L3UL^fJV%1IW~~/Q`.,WRAr</8@$.k|uyK-w^d:k|{h!%(};|)OY9^lu=' . $string);
|
||||
}
|
||||
|
||||
public function setSessionId($sessionId) {
|
||||
$this->sessionId = $sessionId;
|
||||
}
|
||||
|
||||
public function toCurlCmd($method, $url, $data = null) {
|
||||
$cmd = 'curl';
|
||||
|
||||
$addMethod = true;
|
||||
if ($method == 'GET') $addMethod = false;
|
||||
if ($method == 'POST' && count($data)) $addMethod = false;
|
||||
if ($addMethod) $cmd .= ' -X ' . $method;
|
||||
|
||||
if ($data) {
|
||||
foreach ($data as $k => $v) {
|
||||
$cmd .= ' -F "' . $k . '=' . rawurlencode($v) . '"';
|
||||
}
|
||||
}
|
||||
|
||||
$cmd .= ' ' . "'" . $url . "'";
|
||||
return $cmd;
|
||||
}
|
||||
|
||||
public function exec($method, $path, $query = null, $data = null) {
|
||||
$url = $this->baseUrl . '/' . $path;
|
||||
|
||||
if (!$query) $query = array();
|
||||
if ($this->sessionId) $query['session'] = $this->sessionId;
|
||||
if (count($query)) $url .= '?' . http_build_query($query);
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
if ($data) curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
||||
if ($method == 'PATCH' || $method == 'PUT' || $method == 'DELETE') {
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
|
||||
}
|
||||
|
||||
$cmd = $this->toCurlCmd($method, $url, $data);
|
||||
echo $cmd . "\n";
|
||||
|
||||
$content = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
$output = json_decode($content, true);
|
||||
if ($output === null) throw new Exception('Invalid response: ' . $content . "\n\nCommand: " . $cmd . "\n");
|
||||
if (isset($output['error'])) throw new Exception('API error: ' . $content . "\n\nCommand: " . $cmd . "\n");
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
public function login($email, $password, $clientId) {
|
||||
$method = 'POST';
|
||||
$path = 'sessions';
|
||||
$data = array(
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'client_id' => $clientId,
|
||||
);
|
||||
|
||||
return $this->exec($method, $path, null, $data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Config {
|
||||
|
||||
protected $dirPath = null;
|
||||
|
||||
public function __construct($dirPath) {
|
||||
$this->dirPath = $dirPath;
|
||||
}
|
||||
|
||||
protected function load() {
|
||||
$c = @file_get_contents($this->dirPath . '/config.json');
|
||||
$c = json_decode($c, true);
|
||||
if ($c === null) $c = array();
|
||||
if (!isset($c['last_sync_id'])) $c['last_sync_id'] = 0;
|
||||
if (!isset($c['last_sync_time'])) $c['last_sync_time'] = 0;
|
||||
if (!isset($c['folder_items'])) $c['folder_items'] = array();
|
||||
if (!isset($c['client_id'])) $c['client_id'] = null;
|
||||
if (!isset($c['session_id'])) $c['session_id'] = null;
|
||||
return $c;
|
||||
}
|
||||
|
||||
protected function save($c) {
|
||||
file_put_contents($this->dirPath . '/config.json', json_encode($c));
|
||||
}
|
||||
|
||||
public function get($name) {
|
||||
$c = $this->load();
|
||||
if (!isset($c[$name])) throw new Exception('Invalid key name: ' . $name);
|
||||
return $c[$name];
|
||||
}
|
||||
|
||||
public function set($name, $value) {
|
||||
$c = $this->load();
|
||||
$c[$name] = $value;
|
||||
$this->save($c);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class BaseItem {
|
||||
|
||||
private $title = '';
|
||||
private $body = '';
|
||||
private $id;
|
||||
private $parentId;
|
||||
private $isFolder;
|
||||
private $modTime;
|
||||
|
||||
public function setTitle($v) { $this->title = $v; }
|
||||
public function setBody($v) { $this->body = $v; }
|
||||
public function setId($v) { $this->id = $v; }
|
||||
public function setParentId($v) { $this->parentId = $v; }
|
||||
public function setIsFolder($v) { $this->isFolder = $v; }
|
||||
public function setModTime($v) { $this->modTime = $v; }
|
||||
|
||||
public function title() { return $this->title; }
|
||||
public function body() { return $this->body; }
|
||||
public function id() { return $this->id; }
|
||||
public function parentId() { return $this->parentId; }
|
||||
public function isFolder() { return $this->isFolder; }
|
||||
public function isNote() { return !$this->isFolder(); }
|
||||
public function modTime() { return $this->modTime; }
|
||||
|
||||
public function toApiArray() {
|
||||
$output = array(
|
||||
'title' => $this->title(),
|
||||
'parent_id' => $this->parentId(),
|
||||
);
|
||||
if ($this->isNote()) $output['body'] = $this->body();
|
||||
return $output;
|
||||
}
|
||||
|
||||
public function fromApiArray($type, $array) {
|
||||
$this->setTitle($array['title']);
|
||||
if ($type == 'note') $this->setBody($array['body']);
|
||||
$this->setId($array['id']);
|
||||
$this->setParentId($array['parent_id']);
|
||||
$this->setIsFolder($type == 'folder');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class BaseItems {
|
||||
|
||||
private $items = array();
|
||||
|
||||
private function getBaseItems($dir, $parentId, &$output) {
|
||||
$paths = glob($dir . '/*');
|
||||
foreach ($paths as $path) {
|
||||
$isFolder = is_dir($path);
|
||||
$modTime = filemtime($path);
|
||||
|
||||
$o = new BaseItem();
|
||||
$o->setTitle(basename($path));
|
||||
$o->setId(Api::createId($parentId . '_' . $o->title()));
|
||||
$o->setParentId($parentId);
|
||||
$o->setIsFolder($isFolder);
|
||||
$o->setModTime($modTime);
|
||||
|
||||
if (!$isFolder) $o->setBody(file_get_contents($path));
|
||||
$output[] = $o;
|
||||
if ($isFolder) $this->getBaseItems($path, $o->id(), $output);
|
||||
}
|
||||
}
|
||||
|
||||
public function fromPath($path) {
|
||||
$this->items = array();
|
||||
$this->getBaseItems($path, null, $this->items);
|
||||
}
|
||||
|
||||
public function all() {
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
public function add($item) {
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
public function setById($id, $item) {
|
||||
$found = false;
|
||||
for ($i = 0; $i < count($this->items); $i++) {
|
||||
$it = $this->items[$i];
|
||||
if ($it->id() == $id) {
|
||||
$found = true;
|
||||
$this->items[$i] = $item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function byId($id) {
|
||||
foreach ($this->all() as $item) {
|
||||
if ($item->id() == $id) return $item;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function itemFullPath($item) {
|
||||
if (!$item->parentId()) return $item->title();
|
||||
$parent = $this->byId($item->parentId());
|
||||
if (!$parent) throw new Exception('Cannot find parent with ID ' . $item->parentId());
|
||||
return escapePath($this->itemFullPath($parent) . '/' . $item->title());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$shortopts = "";
|
||||
$longopts = array(
|
||||
"config:",
|
||||
"sync",
|
||||
);
|
||||
|
||||
$flags = getopt($shortopts, $longopts);
|
||||
|
||||
if (!isset($flags['config'])) $flags['config'] = '/home/laurent/src/notes/cli-client/.config';
|
||||
|
||||
$config = new Config($flags['config']);
|
||||
|
||||
$dataPath = '/home/laurent/src/notes/cli-client/test_' . $config->get('client_id');
|
||||
|
||||
$api = new Api('http://127.0.0.1:8000');
|
||||
$session = $api->login('test@example.com', '12345678', $config->get('client_id'));
|
||||
$api->setSessionId($session['id']);
|
||||
|
||||
if (array_key_exists('sync', $flags)) {
|
||||
$syncStartTime = time();
|
||||
$lastSyncTime = $config->get('last_sync_time');
|
||||
$BaseItems = new BaseItems();
|
||||
$BaseItems->fromPath($dataPath);
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Get latest changes from API
|
||||
// ------------------------------------------------------------------------------------------
|
||||
|
||||
$response = $api->exec('GET', 'synchronizer', array('last_id' => $config->get('last_sync_id')));
|
||||
// $response = $api->exec('GET', 'synchronizer', array('last_id' => 80));
|
||||
|
||||
$pathMap = array();
|
||||
$folders = array();
|
||||
$notes = array();
|
||||
$maxId = null;
|
||||
foreach ($response['items'] as $item) {
|
||||
$BaseItem = new BaseItem();
|
||||
|
||||
switch ($item['type']) {
|
||||
|
||||
case 'create':
|
||||
case 'update':
|
||||
|
||||
$resource = $api->exec('GET', $item['item_type'] . 's/' . $item['item_id']);
|
||||
$BaseItem->fromApiArray($item['item_type'], $resource);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
throw new Exception('Unsupported action type: ' . $item['type']);
|
||||
|
||||
}
|
||||
|
||||
$BaseItems->setById($BaseItem->id(), $BaseItem);
|
||||
|
||||
$maxId = max($item['id'], $maxId);
|
||||
}
|
||||
|
||||
foreach ($BaseItems->all() as $item) {
|
||||
$relativePath = $BaseItems->itemFullPath($item);
|
||||
$path = $dataPath . '/' . $relativePath;
|
||||
|
||||
foreach (array('folder', 'note') as $itemType) {
|
||||
if ($item->isFolder() && $itemType == 'folder') {
|
||||
@mkdir($path, 0755, true); // Ignore "File exists" warning
|
||||
if (!is_dir($path)) throw new Exception('Could not create folder at ' . $path);
|
||||
}
|
||||
|
||||
if ($item->isNote() && $itemType == 'note') {
|
||||
if ($item->body() !== file_get_contents($path)) {
|
||||
file_put_contents($path, $item->body());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pathMap[$item->id()] = $relativePath;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Send changed notes and folders to API
|
||||
// ------------------------------------------------------------------------------------------
|
||||
|
||||
foreach ($BaseItems->all() as $item) {
|
||||
if ($item->modTime() < $lastSyncTime) continue;
|
||||
|
||||
if ($item->isFolder()) {
|
||||
$api->exec('PUT', 'folders/' . $item->id(), null, $item->toApiArray());
|
||||
} else {
|
||||
$api->exec('PUT', 'notes/' . $item->id(), null, $item->toApiArray());
|
||||
}
|
||||
}
|
||||
|
||||
$config->set('last_sync_time', $syncStartTime);
|
||||
$config->set('folder_items', json_encode($pathMap));
|
||||
if ($maxId !== null) $config->set('last_sync_id', $maxId);
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
# FOLDER2/note1
|
||||
|
||||
Modified on client 2
|
@@ -1 +0,0 @@
|
||||
# NOTE 1
|
@@ -1 +0,0 @@
|
||||
# NOTE 2
|
@@ -1 +0,0 @@
|
||||
test subnode
|
@@ -1,3 +0,0 @@
|
||||
# FOLDER2/note1
|
||||
|
||||
Modified on client 2
|
@@ -1 +0,0 @@
|
||||
# NOTE 1
|
@@ -1 +0,0 @@
|
||||
# NOTE 2
|
@@ -1 +0,0 @@
|
||||
test subnode
|
@@ -4,7 +4,15 @@
|
||||
{
|
||||
"path": ".",
|
||||
"folder_exclude_patterns": [
|
||||
"var"
|
||||
"var",
|
||||
"vendor",
|
||||
"QtClient/build-JoplinQtClient-Visual_C_32_bits-Debug",
|
||||
"QtClient/data/resources",
|
||||
"app/data/uploads"
|
||||
],
|
||||
"file_exclude_patterns": [
|
||||
"*.pro.user",
|
||||
"*.pro.user.*"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
"es2015",
|
||||
"react",
|
||||
"stage-0"
|
||||
],
|
||||
"plugins": [
|
||||
[
|
||||
"transform-decorators-legacy"
|
||||
]
|
||||
]
|
||||
}
|
39
spa_client/.gitignore
vendored
39
spa_client/.gitignore
vendored
@@ -1,39 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directory
|
||||
node_modules
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Ignore files for distribution
|
||||
dist/*
|
||||
|
||||
# OSX .DS_Store files
|
||||
.DS_Store
|
@@ -1,19 +0,0 @@
|
||||
React/Redux Example Todo Application
|
||||
====================================
|
||||
|
||||
An example application demonstrating how to build a Todo with React and Redux.
|
||||
|
||||
# How to run
|
||||
|
||||
Open up a terminal and execute:
|
||||
|
||||
```term
|
||||
$ npm install
|
||||
$ npm start
|
||||
```
|
||||
|
||||
Then open up your browser and navigate to [http://localhost:3000/](http://localhost:3000/).
|
||||
|
||||
# License
|
||||
|
||||
Apache 2.0
|
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"name": "stormpath-react-redux-todo-example-application",
|
||||
"version": "0.0.1",
|
||||
"description": "React/Redux example todo application.",
|
||||
"homepage": "https://github.com/typerandom/stormpath-react-redux-todo-example-application",
|
||||
"author": "Stormpath, 2016",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"dev": "node ./server ./src/ ./webpack.dev.config",
|
||||
"build": "rm -rf ./dist/ && mkdir ./dist/ && cp -r ./src/* ./dist && rm -rf ./dist/js/* && webpack",
|
||||
"start": "npm run build && node ./server"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/typerandom/stormpath-react-redux-todo-example-application.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/typerandom/stormpath-react-redux-todo-example-application/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.3.26",
|
||||
"babel-loader": "^6.2.0",
|
||||
"babel-plugin-react-transform": "^2.0.0",
|
||||
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
||||
"babel-preset-es2015": "^6.3.13",
|
||||
"babel-preset-react": "^6.3.13",
|
||||
"babel-preset-stage-0": "^6.3.13",
|
||||
"babel-runtime": "^6.3.19",
|
||||
"express": "^4.13.4",
|
||||
"morgan": "^1.7.0",
|
||||
"open": "0.0.5",
|
||||
"react": "^0.14.7",
|
||||
"react-dom": "^0.14.7",
|
||||
"react-redux": "^4.4.6",
|
||||
"redux": "^3.4.0",
|
||||
"webpack": "^1.12.13",
|
||||
"webpack-dev-middleware": "^1.5.1",
|
||||
"object-path-immutable": "^0.5.1",
|
||||
"deepcopy": "^0.6.3"
|
||||
}
|
||||
}
|
@@ -1,32 +0,0 @@
|
||||
var open = require('open');
|
||||
var path = require('path');
|
||||
var morgan = require('morgan');
|
||||
var express = require('express');
|
||||
var webpack = require('webpack');
|
||||
var webpackDevMiddleware = require('webpack-dev-middleware');
|
||||
|
||||
var config = require('./webpack.config');
|
||||
var compiler = webpack(config);
|
||||
|
||||
var app = express();
|
||||
|
||||
app.use(morgan('dev'));
|
||||
|
||||
app.use(webpackDevMiddleware(compiler, {
|
||||
noInfo: true,
|
||||
publicPath: config.output.publicPath
|
||||
}));
|
||||
|
||||
app.use(express.static('./dist/'));
|
||||
|
||||
app.get('*', function (req, res){
|
||||
res.sendFile(path.resolve(__dirname, './dist/', 'index.html'))
|
||||
});
|
||||
|
||||
app.listen(3000, function (err) {
|
||||
if (err) {
|
||||
return console.error(err);
|
||||
}
|
||||
|
||||
console.log('Web listening at http://localhost:3000/.');
|
||||
});
|
@@ -1,39 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title></title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
a, a:active, a:visited {
|
||||
text-decoration: none;
|
||||
}
|
||||
.selected {
|
||||
border: 1px solid black;
|
||||
}
|
||||
.level-1 {
|
||||
margin-left: 0;
|
||||
}
|
||||
.level-2 {
|
||||
margin-left: 20px;
|
||||
}
|
||||
.level-3 {
|
||||
margin-left: 40px;
|
||||
}
|
||||
.folder-list {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
padding: 15px;
|
||||
}
|
||||
.note-list {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
padding: 15px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container"></div>
|
||||
<script src="/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
@@ -1,420 +0,0 @@
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom'
|
||||
import { createStore } from 'redux';
|
||||
import { Provider } from 'react-redux'
|
||||
import RootFolderList from 'components/root-folder-list.jsx';
|
||||
import NoteList from 'components/note-list.jsx';
|
||||
import { reducer } from './reducer.jsx'
|
||||
|
||||
let defaultState = {
|
||||
'myButtonLabel': 'click',
|
||||
items: [
|
||||
{ id: 101, title: 'folder 1', type: 1, parent_id: 0 },
|
||||
{ id: 102, title: 'folder 2', type: 1, parent_id: 0 },
|
||||
{ id: 103, title: 'folder 3', type: 1, parent_id: 101 },
|
||||
{ id: 104, title: 'folder 4', type: 1, parent_id: 101 },
|
||||
{ id: 105, title: 'folder 5', type: 1, parent_id: 0 },
|
||||
{ id: 106, title: 'folder 6', type: 1, parent_id: 105 },
|
||||
{ id: 1, type: 2, parent_id: 101, title: 'one', body: '111 dsqfdsmlk mqkfkdq sfkl qlmskfqm' },
|
||||
{ id: 2, type: 2, parent_id: 101, title: 'two', body: '222 dsqfdsmlk mqkfkdq sfkl 222 qlmskfqm' },
|
||||
{ id: 3, type: 2, parent_id: 103, title: 'three', body: '33 dsqfdsmlk mqkfkdq sfkl 33 qlmskfqm' },
|
||||
{ id: 4, type: 2, parent_id: 103, title: 'four', body: '4222 dsqfdsmlk mqkfkdq sfkl 222 qlmskfqm' },
|
||||
{ id: 5, type: 2, parent_id: 103, title: 'five', body: '5222 dsqfdsmlk mqkfkdq sfkl 222 qlmskfqm' },
|
||||
{ id: 6, type: 2, parent_id: 104, title: 'six', body: '6222 dsqfdsmlk mqkfkdq sfkl 222 qlmskfqm' },
|
||||
{ id: 7, type: 2, parent_id: 104, title: 'seven', body: '7222 dsqfdsmlk mqkfkdq sfkl 222 qlmskfqm' },
|
||||
],
|
||||
selectedFolderId: null,
|
||||
selectedNoteId: null,
|
||||
expandedFolderIds: [],
|
||||
}
|
||||
|
||||
let store = createStore(reducer, defaultState)
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<RootFolderList />
|
||||
<NoteList />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>,
|
||||
document.getElementById('container')
|
||||
)
|
||||
|
||||
|
||||
// var defaultState = {
|
||||
// folders: [],
|
||||
// todo: {
|
||||
// items: []
|
||||
// },
|
||||
// selectedFolderId: null
|
||||
// };
|
||||
|
||||
// function addTodo(message) {
|
||||
// return {
|
||||
// type: 'ADD_TODO',
|
||||
// message: message,
|
||||
// completed: false
|
||||
// };
|
||||
// }
|
||||
|
||||
// function completeTodo(index) {
|
||||
// return {
|
||||
// type: 'COMPLETE_TODO',
|
||||
// index: index
|
||||
// };
|
||||
// }
|
||||
|
||||
// function deleteTodo(index) {
|
||||
// return {
|
||||
// type: 'DELETE_TODO',
|
||||
// index: index
|
||||
// };
|
||||
// }
|
||||
|
||||
// function clearTodo() {
|
||||
// return {
|
||||
// type: 'CLEAR_TODO'
|
||||
// };
|
||||
// }
|
||||
|
||||
// function createId() {
|
||||
// return Math.round(Math.random() * 99999);
|
||||
// }
|
||||
|
||||
// function folderIndex(state, id) {
|
||||
// for (var i = 0; i < state.folders.length; i++) {
|
||||
// if (state.folders[i].id == id) return i;
|
||||
// }
|
||||
// return -1;
|
||||
// }
|
||||
|
||||
// function folderById(state, id) {
|
||||
// var i = folderIndex(state, id);
|
||||
// return i >= 0 ? state.folders[i] : null;
|
||||
// }
|
||||
|
||||
// function todoApp(state, action) {
|
||||
|
||||
// switch (action.type) {
|
||||
|
||||
// case 'ADD_FOLDER':
|
||||
|
||||
// var folder = {
|
||||
// name: action.name,
|
||||
// id: createId(),
|
||||
// selected: false
|
||||
// };
|
||||
|
||||
// state = immutable.push(state, 'folders', folder);
|
||||
// state = immutable.set(state, 'selectedFolderId', folder.id);
|
||||
// return state;
|
||||
|
||||
// case 'DELETE_FOLDER':
|
||||
|
||||
// var folders = deepcopy(state.folders);
|
||||
// var index = folderIndex(state, action.id);
|
||||
// if (index < 0) return state;
|
||||
// folders.splice(index, 1);
|
||||
// return immutable.set(state, 'folders', folders);
|
||||
|
||||
// case 'SET_SELECTED_FOLDER':
|
||||
|
||||
// return immutable.set(state, 'selectedFolderId', action.id);
|
||||
|
||||
// case 'SET_FOLDER_NAME':
|
||||
|
||||
// var idx = folderIndex(state, action.id);
|
||||
// return immutable.set(state, 'folders.' + idx + '.name', action.name);
|
||||
|
||||
// case 'ADD_TODO':
|
||||
// var items = [].concat(state.todo.items);
|
||||
// return Object.assign({}, state, {
|
||||
// todo: {
|
||||
// items: items.concat([{
|
||||
// message: action.message,
|
||||
// completed: false
|
||||
// }])
|
||||
// }
|
||||
// });
|
||||
|
||||
// case 'COMPLETE_TODO':
|
||||
// var items = [].concat(state.todo.items);
|
||||
|
||||
// items[action.index].completed = true;
|
||||
|
||||
// return Object.assign({}, state, {
|
||||
// todo: {
|
||||
// items: items
|
||||
// }
|
||||
// });
|
||||
|
||||
// case 'DELETE_TODO':
|
||||
// var items = [].concat(state.todo.items);
|
||||
|
||||
// items.splice(action.index, 1);
|
||||
|
||||
// return Object.assign({}, state, {
|
||||
// todo: {
|
||||
// items: items
|
||||
// }
|
||||
// });
|
||||
|
||||
// case 'CLEAR_TODO':
|
||||
// return Object.assign({}, state, {
|
||||
// todo: {
|
||||
// items: []
|
||||
// }
|
||||
// });
|
||||
|
||||
// default:
|
||||
// return state;
|
||||
// }
|
||||
// }
|
||||
|
||||
// var store = createStore(todoApp, defaultState);
|
||||
|
||||
// class AddTodoForm extends React.Component {
|
||||
// state = {
|
||||
// message: ''
|
||||
// };
|
||||
|
||||
// onFormSubmit(e) {
|
||||
// e.preventDefault();
|
||||
// store.dispatch(addTodo(this.state.message));
|
||||
// this.setState({ message: '' });
|
||||
// }
|
||||
|
||||
// onMessageChanged(e) {
|
||||
// var message = e.target.value;
|
||||
// this.setState({ message: message });
|
||||
// }
|
||||
|
||||
// render() {
|
||||
// return (
|
||||
// <form onSubmit={this.onFormSubmit.bind(this)}>
|
||||
// <input type="text" placeholder="Todo..." onChange={this.onMessageChanged.bind(this)} value={this.state.message} />
|
||||
// <input type="submit" value="Add" />
|
||||
// </form>
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// class AddFolderForm extends React.Component {
|
||||
// state = {
|
||||
// name: ''
|
||||
// };
|
||||
|
||||
// onFormSubmit(e) {
|
||||
// e.preventDefault();
|
||||
// store.dispatch({
|
||||
// type: 'ADD_FOLDER',
|
||||
// name: this.state.name
|
||||
// });
|
||||
// this.setState({ name: '' });
|
||||
// }
|
||||
|
||||
// onInputChange(e) {
|
||||
// this.setState({ name: e.target.value });
|
||||
// }
|
||||
|
||||
// render() {
|
||||
// return (
|
||||
// <form onSubmit={this.onFormSubmit.bind(this)}>
|
||||
// <input type="text" placeholder="Folder..." onChange={this.onInputChange.bind(this)} value={this.state.name} />
|
||||
// <input type="submit" value="Add" />
|
||||
// </form>
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// class TodoItem extends React.Component {
|
||||
// onDeleteClick() {
|
||||
// store.dispatch(deleteTodo(this.props.index));
|
||||
// }
|
||||
|
||||
// onCompletedClick() {
|
||||
// store.dispatch(completeTodo(this.props.index));
|
||||
// }
|
||||
|
||||
// render() {
|
||||
// return (
|
||||
// <li>
|
||||
// <a href="#" onClick={this.onCompletedClick.bind(this)} style={{textDecoration: this.props.completed ? 'line-through' : 'none'}}>{this.props.message.trim()}</a>
|
||||
// <a href="#" onClick={this.onDeleteClick.bind(this)} style={{textDecoration: 'none'}}>[x]</a>
|
||||
// </li>
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// class FolderItem extends React.Component {
|
||||
// onDeleteClick() {
|
||||
// store.dispatch({
|
||||
// type: 'DELETE_FOLDER',
|
||||
// id: this.props.item.id
|
||||
// });
|
||||
// }
|
||||
|
||||
// onSelected() {
|
||||
// store.dispatch({
|
||||
// type: 'SET_SELECTED_FOLDER',
|
||||
// id: this.props.item.id
|
||||
// });
|
||||
// }
|
||||
|
||||
// render() {
|
||||
// let selectedClass = this.props.selected ? 'selected' : '';
|
||||
// return (
|
||||
// <li>
|
||||
// <a href="#" className={selectedClass} onClick={this.onSelected.bind(this)}>{this.props.item.name} ({this.props.item.id})</a> <a href="#" onClick={this.onDeleteClick.bind(this)}>[x]</a>
|
||||
// </li>
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// class FolderList extends React.Component {
|
||||
// state = {
|
||||
// folders: [],
|
||||
// selectedFolderId: null,
|
||||
// folderName: '',
|
||||
// lastSelectedFolderId: null
|
||||
// };
|
||||
|
||||
// componentWillMount() {
|
||||
// store.subscribe(() => {
|
||||
// var state = store.getState();
|
||||
// this.setState({
|
||||
// folders: state.folders,
|
||||
// selectedFolderId: state.selectedFolderId
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
// folderNameInput_keyPress(e) {
|
||||
// if (e.key == 'Enter') {
|
||||
// console.info(this.state.folderName);
|
||||
// store.dispatch({
|
||||
// type: 'SET_FOLDER_NAME',
|
||||
// name: this.state.folderName,
|
||||
// id: this.state.selectedFolderId
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// folderNameInput_onChange(e) {
|
||||
// this.setState({ folderName: e.target.value });
|
||||
// }
|
||||
|
||||
// render() {
|
||||
// var items = [];
|
||||
|
||||
// this.state.folders.forEach((item, index) => {
|
||||
// let isSelected = this.state.selectedFolderId == item.id;
|
||||
// items.push(
|
||||
// <FolderItem
|
||||
// key={index}
|
||||
// index={index}
|
||||
// item={item}
|
||||
// selected={isSelected} />
|
||||
// );
|
||||
// });
|
||||
|
||||
// if (!items.length) {
|
||||
// return (
|
||||
// <p>
|
||||
// <i>No folder.</i>
|
||||
// </p>
|
||||
// );
|
||||
// }
|
||||
|
||||
// var selectedFolder = folderById(this.state, this.state.selectedFolderId);
|
||||
// var selectedFolderId = selectedFolder ? selectedFolder.id : null;
|
||||
// if (selectedFolderId !== this.state.lastSelectedFolderId) {
|
||||
// this.state.folderName = selectedFolder ? selectedFolder.name : '';
|
||||
// this.state.lastSelectedFolderId = selectedFolderId;
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <div>
|
||||
// <ol>{ items }</ol>
|
||||
// <input type="text" onKeyPress={this.folderNameInput_keyPress.bind(this)} onChange={this.folderNameInput_onChange.bind(this)} value={this.state.folderName} />
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// class TodoList extends React.Component {
|
||||
// state = {
|
||||
// items: []
|
||||
// };
|
||||
|
||||
// componentWillMount() {
|
||||
// store.subscribe(() => {
|
||||
// var state = store.getState();
|
||||
// this.setState({
|
||||
// items: state.todo.items
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
// render() {
|
||||
// var items = [];
|
||||
|
||||
// this.state.items.forEach((item, index) => {
|
||||
// items.push(<TodoItem
|
||||
// key={index}
|
||||
// index={index}
|
||||
// message={item.message}
|
||||
// completed={item.completed}
|
||||
// />);
|
||||
// });
|
||||
|
||||
// if (!items.length) {
|
||||
// return (
|
||||
// <p>
|
||||
// <i>Please add something to do.</i>
|
||||
// </p>
|
||||
// );
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <ol>{ items }</ol>
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// ReactDOM.render(
|
||||
// <div>
|
||||
// <h1>Todo</h1>
|
||||
// <AddTodoForm />
|
||||
// <AddFolderForm />
|
||||
// <FolderList />
|
||||
// <TodoList />
|
||||
// </div>,
|
||||
// document.getElementById('container')
|
||||
// );
|
||||
|
||||
// store.dispatch({
|
||||
// type: 'ADD_FOLDER',
|
||||
// name: 'aaaa'
|
||||
// });
|
||||
|
||||
// store.dispatch({
|
||||
// type: 'ADD_FOLDER',
|
||||
// name: 'bbbb'
|
||||
// });
|
||||
|
||||
// store.dispatch({
|
||||
// type: 'ADD_FOLDER',
|
||||
// name: 'cccc'
|
||||
// });
|
@@ -1,39 +0,0 @@
|
||||
import React from 'react';
|
||||
import Folder from './folder.jsx';
|
||||
import { connect } from 'react-redux';
|
||||
import * as fi from 'models/folder-item.jsx';
|
||||
|
||||
class FolderListComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
let elements = [];
|
||||
let level = Number(this.props.level) + 1;
|
||||
let className = 'level-' + level;
|
||||
className += ' folder-list';
|
||||
|
||||
this.props.items.forEach((item, index) => {
|
||||
if (this.props.parentId != item.parent_id) return;
|
||||
let selected = this.props.selectedFolderId == item.id;
|
||||
let children = fi.children(this.props.items, item.id);
|
||||
elements.push(<Folder level={level} title={item.title} key={item.id} id={item.id} expandedFolderIds={this.props.expandedFolderIds} selectedFolderId={this.props.selectedFolderId} children={children} />);
|
||||
});
|
||||
|
||||
return <div className={className}>{elements}</div>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = function(state) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = function(dispatch) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const FolderList = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(FolderListComponent)
|
||||
|
||||
export default FolderList
|
@@ -1,50 +0,0 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux'
|
||||
import FolderList from './folder-list.jsx';
|
||||
|
||||
class FolderComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
let selectedClass = this.props.selectedFolderId == this.props.id ? 'selected' : '';
|
||||
let elements = [];
|
||||
let key = 'note-name-' + this.props.id;
|
||||
|
||||
elements.push(
|
||||
<div key={key} onClick={this.props.onClick.bind(this)} className={selectedClass} id="{this.props.id}">{this.props.title}</div>
|
||||
);
|
||||
|
||||
var showChildren = this.props.children.length && this.props.expandedFolderIds.indexOf(this.props.id) >= 0;
|
||||
|
||||
if (showChildren) {
|
||||
key = 'folder-list-' + this.props.id;
|
||||
elements.push(
|
||||
<FolderList key={key} level={this.props.level} parentId={this.props.id} items={this.props.children} selectedFolderId={this.props.selectedFolderId} />
|
||||
);
|
||||
}
|
||||
|
||||
return <div>{elements}</div>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const Folder = connect(
|
||||
function(state) { return {} },
|
||||
|
||||
function(dispatch) {
|
||||
return {
|
||||
onClick: function(event) {
|
||||
dispatch({
|
||||
type: 'SELECT_FOLDER',
|
||||
id: this.props.id,
|
||||
});
|
||||
dispatch({
|
||||
type: 'TOGGLE_FOLDER',
|
||||
id: this.props.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
)(FolderComponent)
|
||||
|
||||
export default Folder
|
@@ -1,11 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
class MyButton extends React.Component {
|
||||
|
||||
render() {
|
||||
return <button onClick={this.props.onClick}>{this.props.label}</button>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default MyButton
|
@@ -1,28 +0,0 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
class NoteListItemComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
let className = this.props.selected ? 'selected' : '';
|
||||
return <div onClick={this.props.onClick.bind(this)} className={className}>{this.props.title}</div>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const NoteListItem = connect(
|
||||
function(state) { return {} },
|
||||
function(dispatch) {
|
||||
return {
|
||||
onClick: function(event) {
|
||||
console.info(this.props.id);
|
||||
dispatch({
|
||||
type: 'SELECT_NOTE',
|
||||
id: this.props.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
)(NoteListItemComponent)
|
||||
|
||||
export default NoteListItem
|
@@ -1,37 +0,0 @@
|
||||
import React from 'react';
|
||||
import NoteListItem from './note-list-item.jsx';
|
||||
import { connect } from 'react-redux'
|
||||
import * as fi from 'models/folder-item.jsx';
|
||||
|
||||
class NoteListComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
let elements = [];
|
||||
|
||||
this.props.items.forEach((item, index) => {
|
||||
let selected = this.props.selectedNoteId == item.id;
|
||||
elements.push(<NoteListItem selected={selected} key={item.id} title={item.title} body={item.body} id={item.id} />);
|
||||
});
|
||||
|
||||
return <div className="note-list">{elements}</div>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = function(state) {
|
||||
return {
|
||||
items: fi.notes(state.items, state.selectedFolderId),
|
||||
selectedNoteId: state.selectedNoteId,
|
||||
};
|
||||
}
|
||||
|
||||
const mapDispatchToProps = function(dispatch) {
|
||||
return { }
|
||||
}
|
||||
|
||||
const NoteList = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(NoteListComponent)
|
||||
|
||||
export default NoteList
|
@@ -1,31 +0,0 @@
|
||||
import React from 'react';
|
||||
import FolderList from './folder-list.jsx';
|
||||
import { connect } from 'react-redux';
|
||||
import * as fi from 'models/folder-item.jsx';
|
||||
|
||||
class RootFolderListComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
return <FolderList expandedFolderIds={this.props.expandedFolderIds} level="0" parentId="0" items={this.props.items} selectedFolderId={this.props.selectedFolderId} />
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = function(state) {
|
||||
return {
|
||||
items: fi.folders(state.items),
|
||||
selectedFolderId: state.selectedFolderId,
|
||||
expandedFolderIds: state.expandedFolderIds,
|
||||
};
|
||||
}
|
||||
|
||||
const mapDispatchToProps = function(dispatch) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const RootFolderList = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(RootFolderListComponent)
|
||||
|
||||
export default RootFolderList
|
@@ -1,42 +0,0 @@
|
||||
export function rootItems(items) {
|
||||
var output = [];
|
||||
items.forEach((item, index) => {
|
||||
if (!item.parent_id) return;
|
||||
output.push(item);
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
export function folders(items) {
|
||||
var output = [];
|
||||
items.forEach((item, index) => {
|
||||
if (item.type != 1) return;
|
||||
output.push(item);
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
export function notes(items, folderId) {
|
||||
var output = [];
|
||||
items.forEach((item, index) => {
|
||||
if (item.type != 2) return;
|
||||
if (item.parent_id != folderId) return;
|
||||
output.push(item);
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
export function byId(items, id) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].id == id) return items[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function children(items, id) {
|
||||
var output = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].parent_id == id) output.push(items[i]);
|
||||
}
|
||||
return output;
|
||||
}
|
@@ -1,32 +0,0 @@
|
||||
import deepcopy from 'deepcopy';
|
||||
|
||||
export function reducer(state, action) {
|
||||
switch (action.type) {
|
||||
|
||||
case 'SELECT_FOLDER':
|
||||
|
||||
var state = deepcopy(state);
|
||||
state.selectedFolderId = action.id;
|
||||
return state;
|
||||
|
||||
case 'SELECT_NOTE':
|
||||
|
||||
var state = deepcopy(state);
|
||||
state.selectedNoteId = action.id;
|
||||
return state;
|
||||
|
||||
case 'TOGGLE_FOLDER':
|
||||
|
||||
var state = deepcopy(state);
|
||||
var idx = state.expandedFolderIds.indexOf(action.id);
|
||||
if (idx < 0) {
|
||||
state.expandedFolderIds.push(action.id);
|
||||
} else {
|
||||
state.expandedFolderIds.splice(idx, 1);
|
||||
}
|
||||
return state;
|
||||
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
@@ -1,128 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { render } from 'react-dom'
|
||||
import { connect } from 'react-redux'
|
||||
import { createStore } from 'redux';
|
||||
import immutable from 'object-path-immutable';
|
||||
import { Provider } from 'react-redux'
|
||||
import deepcopy from 'deepcopy';
|
||||
|
||||
// This is the default state of the application. Each
|
||||
// application has only one state, which is an object
|
||||
// with one or more properties.
|
||||
|
||||
let defaultState = {
|
||||
'myButtonLabel': 'click'
|
||||
}
|
||||
|
||||
// The reducer is what processes the actions of the application, such as
|
||||
// button clicks, text changes, etc. It takes a state and an action as
|
||||
// input and must return a state. Important: the state must not be modified
|
||||
// directly. Create a copy first (see `deepcopy`), modify it and return it.
|
||||
|
||||
function reducer(state, action) {
|
||||
switch (action.type) {
|
||||
|
||||
case 'SET_BUTTON_NAME':
|
||||
|
||||
var state = deepcopy(state);
|
||||
state.myButtonLabel = action.name;
|
||||
return state;
|
||||
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
// The store is what essentially links the reducer to the state.
|
||||
|
||||
let store = createStore(reducer, defaultState)
|
||||
|
||||
// Create the button and input components. Those are regular React components.
|
||||
|
||||
class MyButton extends React.Component {
|
||||
|
||||
render() {
|
||||
return <button onClick={this.props.onClick}>{this.props.label}</button>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MyInput extends React.Component {
|
||||
|
||||
render() {
|
||||
return <input onKeyPress={this.props.onKeyPress} type="text" />
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Create the connected components. A connected component (often called "container component")
|
||||
// is a react component that has been connected to the application state. The connection
|
||||
// happens by mapping the state properties to the component properties, and by mapping the
|
||||
// dispatches to the component handlers. This allows better separating the view (React
|
||||
// component) from the model/controller (state and actions).
|
||||
|
||||
const mapStateToButtonProps = function(state) {
|
||||
return { label: state.myButtonLabel };
|
||||
}
|
||||
|
||||
const mapDispatchToButtonProps = function(dispatch) {
|
||||
return {
|
||||
onClick: function() {
|
||||
alert('click');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MyConnectedButton = connect(
|
||||
mapStateToButtonProps,
|
||||
mapDispatchToButtonProps
|
||||
)(MyButton)
|
||||
|
||||
const mapStateToInputProps = function(state) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const mapDispatchToInputProps = function(dispatch) {
|
||||
return {
|
||||
onKeyPress(e) {
|
||||
if (e.key == 'Enter') {
|
||||
dispatch({
|
||||
type: 'SET_BUTTON_NAME',
|
||||
name: e.target.value
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MyConnectionInput = connect(
|
||||
mapStateToInputProps,
|
||||
mapDispatchToInputProps
|
||||
)(MyInput)
|
||||
|
||||
// Create the application. Note that we display the Connected components,
|
||||
// which in turn include the Rect components.
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<MyConnectedButton />
|
||||
<MyConnectionInput />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Render the application via the <Provider> tag. This is a special React-Redux
|
||||
// component that "magically" links the store to the application and components.
|
||||
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>,
|
||||
document.getElementById('container')
|
||||
)
|
@@ -1,38 +0,0 @@
|
||||
var path = require('path');
|
||||
var webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
quiet: true,
|
||||
entry: [
|
||||
'./src/js/app.jsx'
|
||||
],
|
||||
output: {
|
||||
path: __dirname + '/dist/js/',
|
||||
filename: 'app.js',
|
||||
publicPath: '/js/'
|
||||
},
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.(jsx|js)$/,
|
||||
loaders: ['babel'],
|
||||
include: path.join(__dirname, './src/js/')
|
||||
}]
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
components: path.resolve(__dirname, './src/js/components'),
|
||||
models: path.resolve(__dirname, './src/js/models'),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
// new webpack.optimize.UglifyJsPlugin({
|
||||
// minimize: true,
|
||||
// compress: {
|
||||
// warnings: false
|
||||
// }
|
||||
// }),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': '"development"'
|
||||
})
|
||||
]
|
||||
};
|
@@ -1,32 +0,0 @@
|
||||
// NOT USED
|
||||
|
||||
var path = require('path');
|
||||
var webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
entry: [
|
||||
'./src/js/app'
|
||||
],
|
||||
target: 'web',
|
||||
devtool: 'eval-source-map',
|
||||
output: {
|
||||
path: __dirname + '/src/js/',
|
||||
filename: 'app.min.js',
|
||||
publicPath: '/js/'
|
||||
},
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.js$/,
|
||||
loaders: ['babel'],
|
||||
include: path.join(__dirname, './src/js/')
|
||||
}]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.ProvidePlugin({
|
||||
'Promise': 'es6-promise'
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': '"development"'
|
||||
})
|
||||
]
|
||||
};
|
Reference in New Issue
Block a user