merge
13
.github/workflows/build.yml
vendored
|
@ -11,8 +11,8 @@ jobs:
|
|||
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ 1.16.x ]
|
||||
os: [ macos-latest, ubuntu-latest, windows-latest ]
|
||||
go-version: [ 1.18.10 ]
|
||||
os: [ macos-latest, ubuntu-latest, windows-latest ]
|
||||
|
||||
steps:
|
||||
- name: Setup
|
||||
|
@ -59,6 +59,14 @@ jobs:
|
|||
./update-ci.sh ${{secrets.USER}} ${{secrets.TOKEN}} ${{matrix.os}} arm
|
||||
./update-ci.sh ${{secrets.USER}} ${{secrets.TOKEN}} ${{matrix.os}} amd
|
||||
|
||||
- name: Build Native Messaging Host Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: npm run build:nmh-win
|
||||
|
||||
- name: Build Native Messaging Host
|
||||
if: matrix.os != 'windows-latest'
|
||||
run: npm run build:nmh
|
||||
|
||||
- name: Build Front Mac OS
|
||||
if: matrix.os == 'macos-latest'
|
||||
uses: samuelmeuli/action-electron-builder@v1
|
||||
|
@ -109,6 +117,7 @@ jobs:
|
|||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
rm dist/anytypeHelper.exe
|
||||
rm dist/nativeMessagingHost.exe
|
||||
mv dist/*.exe artifacts
|
||||
|
||||
- name: Release
|
||||
|
|
30
.github/workflows/extension.yml
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Publish webextension
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
- name: Build
|
||||
run: |
|
||||
npm ci
|
||||
npm run build
|
||||
- name: Upload & release
|
||||
uses: mnao305/chrome-extension-upload@v4.0.1
|
||||
with:
|
||||
file-path: dist/file.zip
|
||||
extension-id: hogefuga(extension id)
|
||||
client-id: ${{ secrets.CLIENT_ID }}
|
||||
client-secret: ${{ secrets.CLIENT_SECRET }}
|
||||
refresh-token: ${{ secrets.REFRESH_TOKEN }}
|
||||
publish: false
|
|
@ -5,7 +5,7 @@ npx lint-staged --concurrent false
|
|||
# npm run typecheck
|
||||
|
||||
# Checking for secrets
|
||||
gitleaks protect --verbose --redact --staged
|
||||
#gitleaks protect --verbose --redact --staged
|
||||
|
||||
# Checking dependencies' licenses
|
||||
npx license-checker --production --json --out licenses.json
|
||||
|
|
11
dist/.gitignore
vendored
|
@ -7,6 +7,12 @@ main.js
|
|||
main.js.map
|
||||
commands.js
|
||||
bundle-back.js
|
||||
anytypeHelper*
|
||||
nativeMessagingHost*
|
||||
alpha-linux.yml
|
||||
beta-linux.yml
|
||||
builder-debug.yml
|
||||
latest-linux.yml
|
||||
*-arm64/
|
||||
*-unpacked/
|
||||
*.snap
|
||||
|
@ -17,3 +23,8 @@ bundle-back.js
|
|||
*.node
|
||||
*.yml
|
||||
*.yaml
|
||||
nmh.log
|
||||
[0-9]*.js
|
||||
[0-9]*.js.map
|
||||
extension.crx
|
||||
extension.pem
|
73
dist/challenge/index.html
vendored
Normal file
|
@ -0,0 +1,73 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style type="text/css">
|
||||
* { margin: 0px; }
|
||||
|
||||
html, body { height: 100%; }
|
||||
html.dark body { background-color: #171717; color: #a09f92; }
|
||||
|
||||
body { font-family: Helvetica, Arial, sans-serif; font-size: 14px; line-height: 22px; padding: 16px; background-color: #fff; color: #252525; }
|
||||
|
||||
.content { display: flex; align-items: center; justify-content: center; flex-direction: column; }
|
||||
.logo { width: 64px; height: 64px; -webkit-user-select: none; user-select: none; margin: 0px 0px 12px 0px; }
|
||||
.logo img { width: 100% !important; height: 100% !important; }
|
||||
.title { font-size: 22px; line-height: 28px; letter-spacing: -0.48px; font-weight: 700; margin: 0px 0px 24px 0px; }
|
||||
|
||||
.buttons button { -webkit-app-region: no-drag; }
|
||||
|
||||
.buttons { margin-bottom: 1em; text-align: center; }
|
||||
.buttons button {
|
||||
display: inline-block; text-align: center; border: 0px; font-weight: 500; text-decoration: none;
|
||||
height: 30px; line-height: 30px; padding: 0px 16px; border-radius: 4px; transition: background 0.2s ease-in-out;
|
||||
font-size: 14px; vertical-align: middle; position: relative; overflow: hidden; letter-spacing: 0.2px;
|
||||
background: #ffb522; color: #fff; margin-top: 1em; width: 100px;
|
||||
}
|
||||
.buttons button:hover { background: #f09c0e; }
|
||||
</style>
|
||||
<script src="../js/jquery.js" type="text/javascript"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<div class="logo">
|
||||
<img src="../img/icon/app/64x64.png" />
|
||||
</div>
|
||||
<div class="title">Challenge</div>
|
||||
<div id="challenge"></div>
|
||||
|
||||
<div class="buttons">
|
||||
<button id="close"></button>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(() => {
|
||||
const win = $(window);
|
||||
const closeButton = $('#close');
|
||||
const challengeEl = $('#challenge');
|
||||
|
||||
document.title = 'Anytype';
|
||||
|
||||
closeButton.off('click').on('click', e => {
|
||||
e.preventDefault();
|
||||
window.close();
|
||||
});
|
||||
|
||||
win.off('message').on('message', e => {
|
||||
const { challenge, theme, lang } = e.originalEvent.data;
|
||||
|
||||
challengeEl.text(challenge);
|
||||
$('html').attr({ class: theme });
|
||||
|
||||
$.ajax({
|
||||
url: `../lib/json/lang/${lang}.json`,
|
||||
method: 'GET',
|
||||
contentType: 'application/json',
|
||||
success: data => {
|
||||
closeButton.text(data.commonClose);
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
10
dist/extension/css/foreground.css
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
#anytypeWebclipper-container { position: fixed; z-index: 100000; width: 100%; height: 100%; left: 0px; top: 0px; display: none; }
|
||||
|
||||
#anytypeWebclipper-iframe {
|
||||
position: absolute; z-index: 1; width: 800px; height: 600px; background: #fff; left: 50%; top: 50%; margin: -300px 0px 0px -400px;
|
||||
border-radius: 16px; border: 1px solid rgba(172, 169, 152, 0.12); box-shadow: 0px 4px 20px 0px rgba(0, 0, 0, 0.10);
|
||||
}
|
||||
|
||||
#anytypeWebclipper-container .dimmer {
|
||||
position: absolute; z-index: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); left: 0px; top: 0px;
|
||||
}
|
10
dist/extension/iframe/index.html
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>Anytype Web Clipper</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript" src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
BIN
dist/extension/img/icon128x128.png
vendored
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
dist/extension/img/icon16x16.png
vendored
Normal file
After Width: | Height: | Size: 452 B |
BIN
dist/extension/img/icon32x32.png
vendored
Normal file
After Width: | Height: | Size: 785 B |
2
dist/extension/js/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
main.js
|
||||
main.js.map
|
154
dist/extension/js/background.js
vendored
Normal file
|
@ -0,0 +1,154 @@
|
|||
(() => {
|
||||
|
||||
let ports = [];
|
||||
let isInitMenu = false;
|
||||
|
||||
const native = chrome.runtime.connectNative('com.anytype.desktop');
|
||||
|
||||
native.postMessage({ type: 'getPorts' });
|
||||
|
||||
native.onMessage.addListener((msg) => {
|
||||
console.log('[Native]', msg);
|
||||
|
||||
if (msg.error) {
|
||||
console.error(msg.error);
|
||||
};
|
||||
|
||||
switch (msg.type) {
|
||||
case 'launchApp': {
|
||||
sendToActiveTab({ type: 'launchApp', res: msg.response });
|
||||
break;
|
||||
};
|
||||
|
||||
case 'getPorts': {
|
||||
if (msg.response) {
|
||||
for (let pid in msg.response) {
|
||||
ports = msg.response[pid];
|
||||
break;
|
||||
};
|
||||
};
|
||||
break;
|
||||
};
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
native.onDisconnect.addListener(() => {
|
||||
console.log('[Native] Disconnected');
|
||||
});
|
||||
|
||||
chrome.runtime.onInstalled.addListener(details => {
|
||||
if (![ 'install', 'update' ].includes(details.reason)) {
|
||||
return;
|
||||
};
|
||||
|
||||
if (details.reason == 'update') {
|
||||
const { version } = chrome.runtime.getManifest();
|
||||
|
||||
console.log('Updated', details.previousVersion, version);
|
||||
};
|
||||
|
||||
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
||||
getActiveTab(tab => {
|
||||
if (tab && (tabId == tab.id) && (undefined !== changeInfo.url)) {
|
||||
sendToTab(tab, { type: 'hide' });
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
console.log('[Background]', msg.type);
|
||||
|
||||
const res = {};
|
||||
|
||||
switch (msg.type) {
|
||||
case 'launchApp': {
|
||||
native.postMessage({ type: 'launchApp' });
|
||||
break;
|
||||
};
|
||||
|
||||
case 'getPorts': {
|
||||
native.postMessage({ type: 'getPorts' });
|
||||
break;
|
||||
};
|
||||
|
||||
case 'checkPorts': {
|
||||
res.ports = ports;
|
||||
break;
|
||||
};
|
||||
|
||||
case 'init': {
|
||||
initMenu();
|
||||
break;
|
||||
};
|
||||
};
|
||||
|
||||
sendResponse(res);
|
||||
return true;
|
||||
});
|
||||
|
||||
initMenu = async () => {
|
||||
if (isInitMenu) {
|
||||
return;
|
||||
};
|
||||
|
||||
isInitMenu = true;
|
||||
|
||||
chrome.contextMenus.create({
|
||||
id: 'webclipper',
|
||||
title: 'Anytype Web Clipper',
|
||||
contexts: [ 'selection' ]
|
||||
});
|
||||
|
||||
chrome.contextMenus.onClicked.addListener(async () => {
|
||||
const tab = await getActiveTab();
|
||||
|
||||
chrome.scripting.executeScript({
|
||||
target: { tabId: tab.id },
|
||||
function: () => {
|
||||
const sel = window.getSelection();
|
||||
|
||||
let html = '';
|
||||
if (sel.rangeCount) {
|
||||
const container = document.createElement("div");
|
||||
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
|
||||
container.appendChild(sel.getRangeAt(i).cloneContents());
|
||||
};
|
||||
html = container.innerHTML;
|
||||
};
|
||||
|
||||
return html;
|
||||
}
|
||||
}, res => {
|
||||
if (res.length) {
|
||||
sendToTab(tab, { type: 'clickMenu', html: res[0].result });
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
getActiveTab = async () => {
|
||||
const [ tab ] = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
|
||||
return tab;
|
||||
};
|
||||
|
||||
sendToActiveTab = async (msg) => {
|
||||
const tab = await getActiveTab();
|
||||
|
||||
console.log('[sendToActiveTab]', tab, msg);
|
||||
|
||||
await sendToTab(tab, msg);
|
||||
};
|
||||
|
||||
sendToTab = async (tab, msg) => {
|
||||
if (!tab) {
|
||||
return;
|
||||
};
|
||||
|
||||
const response = await chrome.tabs.sendMessage(tab.id, msg);
|
||||
|
||||
console.log('[sendToTab]', tab, msg, response);
|
||||
};
|
||||
|
||||
})();
|
59
dist/extension/js/foreground.js
vendored
Normal file
|
@ -0,0 +1,59 @@
|
|||
(() => {
|
||||
|
||||
const extensionId = 'jkmhmgghdjjbafmkgjmplhemjjnkligf';
|
||||
const body = document.querySelector('body');
|
||||
const container = document.createElement('div');
|
||||
const dimmer = document.createElement('div');
|
||||
const iframe = document.createElement('iframe');
|
||||
|
||||
if (body && !document.getElementById(iframe.id)) {
|
||||
body.appendChild(container);
|
||||
};
|
||||
|
||||
container.id = [ 'anytypeWebclipper', 'container' ].join('-');
|
||||
container.appendChild(iframe);
|
||||
container.appendChild(dimmer);
|
||||
|
||||
iframe.id = [ 'anytypeWebclipper', 'iframe' ].join('-');
|
||||
iframe.src = chrome.runtime.getURL('iframe/index.html');
|
||||
|
||||
dimmer.className = 'dimmer';
|
||||
dimmer.addEventListener('click', () => {
|
||||
container.style.display = 'none';
|
||||
});
|
||||
|
||||
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
console.log('[Foreground]', msg, sender);
|
||||
|
||||
if (sender.id != extensionId) {
|
||||
return false;
|
||||
};
|
||||
|
||||
switch (msg.type) {
|
||||
case 'clickMenu':
|
||||
container.style.display = 'block';
|
||||
break;
|
||||
|
||||
case 'hide':
|
||||
container.style.display = 'none';
|
||||
break;
|
||||
};
|
||||
|
||||
sendResponse({});
|
||||
return true;
|
||||
});
|
||||
|
||||
window.addEventListener('message', (e) => {
|
||||
if (e.origin != `chrome-extension://${extensionId}`) {
|
||||
return;
|
||||
};
|
||||
|
||||
const { data } = e;
|
||||
switch (data.type) {
|
||||
case 'clickClose':
|
||||
container.style.display = 'none';
|
||||
break;
|
||||
};
|
||||
});
|
||||
|
||||
})();
|
38
dist/extension/manifest.json
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Anytype Web Clipper",
|
||||
"description": "Anytype is a next generation software that breaks down barriers between applications, gives back privacy and data ownership to users",
|
||||
"version": "0.0.1",
|
||||
"icons": {
|
||||
"16": "img/icon16x16.png",
|
||||
"128": "img/icon128x128.png"
|
||||
},
|
||||
"options_page": "settings/index.html",
|
||||
"action": {
|
||||
"default_title": "Anytype Web Clipper",
|
||||
"default_popup": "popup/index.html"
|
||||
},
|
||||
"permissions": [
|
||||
"contextMenus",
|
||||
"nativeMessaging",
|
||||
"tabs",
|
||||
"scripting",
|
||||
"activeTab"
|
||||
],
|
||||
"background": {
|
||||
"service_worker": "js/background.js"
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"js": [ "js/foreground.js" ],
|
||||
"css": [ "css/foreground.css" ],
|
||||
"matches": [ "<all_urls>" ]
|
||||
}
|
||||
],
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"resources": [ "iframe/index.html" ],
|
||||
"matches": [ "<all_urls>" ]
|
||||
}
|
||||
]
|
||||
}
|
10
dist/extension/popup/index.html
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>Anytype Web Clipper</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript" src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
7
dist/extension/settings/index.html
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
BIN
dist/img/icon/app/1024x1024.png
vendored
Normal file
After Width: | Height: | Size: 251 KiB |
BIN
dist/img/icon/app/128x128.png
vendored
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
dist/img/icon/app/16x16.png
vendored
Normal file
After Width: | Height: | Size: 452 B |
BIN
dist/img/icon/app/256x256.png
vendored
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
dist/img/icon/app/32x32.png
vendored
Normal file
After Width: | Height: | Size: 785 B |
BIN
dist/img/icon/app/512x512.png
vendored
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
dist/img/icon/app/64x64.png
vendored
Normal file
After Width: | Height: | Size: 2.6 KiB |
3
dist/polyfill.js
vendored
|
@ -1,7 +1,6 @@
|
|||
const RendererEvents = {};
|
||||
|
||||
window.Anytype = window.Anytype || {};
|
||||
window.Anytype.Config = {
|
||||
window.AnytypeGlobalConfig = {
|
||||
debug: {
|
||||
mw: true,
|
||||
},
|
||||
|
|
25
electron.js
|
@ -7,6 +7,7 @@ const storage = require('electron-json-storage');
|
|||
const port = process.env.SERVER_PORT;
|
||||
const protocol = 'anytype';
|
||||
const remote = require('@electron/remote/main');
|
||||
const { installNativeMessagingHost } = require('./electron/js/lib/installNativeMessagingHost.js');
|
||||
const binPath = fixPathForAsarUnpack(path.join(__dirname, 'dist', `anytypeHelper${is.windows ? '.exe' : ''}`));
|
||||
|
||||
// Fix notifications app name
|
||||
|
@ -129,6 +130,8 @@ function createWindow () {
|
|||
MenuManager.initMenu();
|
||||
MenuManager.initTray();
|
||||
|
||||
installNativeMessagingHost();
|
||||
|
||||
ipcMain.removeHandler('Api');
|
||||
ipcMain.handle('Api', (e, id, cmd, args) => {
|
||||
const Api = require('./electron/js/api.js');
|
||||
|
@ -167,18 +170,18 @@ app.on('second-instance', (event, argv) => {
|
|||
deeplinkingUrl = argv.find((arg) => arg.startsWith(`${protocol}://`));
|
||||
};
|
||||
|
||||
if (mainWindow) {
|
||||
if (deeplinkingUrl) {
|
||||
Util.send(mainWindow, 'route', Util.getRouteFromUrl(deeplinkingUrl));
|
||||
};
|
||||
|
||||
if (mainWindow.isMinimized()) {
|
||||
mainWindow.restore();
|
||||
};
|
||||
|
||||
mainWindow.show();
|
||||
mainWindow.focus();
|
||||
if (!mainWindow || !deeplinkingUrl) {
|
||||
return;
|
||||
};
|
||||
|
||||
Util.send(mainWindow, 'route', Util.getRouteFromUrl(deeplinkingUrl));
|
||||
|
||||
if (mainWindow.isMinimized()) {
|
||||
mainWindow.restore();
|
||||
};
|
||||
|
||||
mainWindow.show();
|
||||
mainWindow.focus();
|
||||
});
|
||||
|
||||
app.on('before-quit', (e) => {
|
||||
|
|
|
@ -27,8 +27,6 @@ body { margin: 0px; color: #252525; font-family: Helvetica, Arial, sans-serif; f
|
|||
}
|
||||
.version .copy.active { background-image: url('../img/check.svg'); }
|
||||
|
||||
.buttons { margin-bottom: 1em; text-align: center; }
|
||||
.buttons button { margin-top: 1em; width: 100px; height: 24px; }
|
||||
.link { color: #80a0c2; }
|
||||
.bug-report-link { position: absolute; right: 0.5em; bottom: 0.5em; }
|
||||
|
||||
|
@ -36,13 +34,14 @@ body { margin: 0px; color: #252525; font-family: Helvetica, Arial, sans-serif; f
|
|||
.bug-report-link,
|
||||
.buttons button { -webkit-app-region: no-drag; }
|
||||
|
||||
.buttons { margin-bottom: 1em; text-align: center; }
|
||||
.buttons button {
|
||||
display: inline-block; text-align: center; border: 0px; font-weight: 500; text-decoration: none;
|
||||
height: 30px; line-height: 30px; padding: 0px 16px; border-radius: 4px; transition: background 0.2s ease-in-out;
|
||||
font-size: 14px; vertical-align: middle; position: relative; overflow: hidden; letter-spacing: 0.2px;
|
||||
background: #ffb522; color: #fff;
|
||||
background: #ffb522; color: #fff; margin-top: 1em; width: 100px;
|
||||
}
|
||||
.buttons button:hover { background: #f1981a; }
|
||||
.buttons button:hover { background: #f09c0e; }
|
||||
|
||||
html.dark body { color: #a09f92; }
|
||||
html.dark .title { color: #dfddd3; }
|
||||
|
|
|
@ -6,7 +6,7 @@ $(() => {
|
|||
var versionText = '';
|
||||
var timeout = 0;
|
||||
|
||||
document.title = 'Anytype';
|
||||
$('html').attr({ class: param.theme });
|
||||
|
||||
closeButton.on('click', e => {
|
||||
e.preventDefault();
|
||||
|
@ -46,4 +46,15 @@ $(() => {
|
|||
},
|
||||
});
|
||||
|
||||
function getParam () {
|
||||
var a = location.search.replace(/^\?/, '').split('&');
|
||||
var param = {};
|
||||
|
||||
a.forEach((s) => {
|
||||
var kv = s.split('=');
|
||||
param[kv[0]] = kv[1];
|
||||
});
|
||||
return param;
|
||||
};
|
||||
|
||||
});
|
|
@ -1,14 +0,0 @@
|
|||
function getParam () {
|
||||
var a = location.search.replace(/^\?/, '').split('&');
|
||||
var param = {};
|
||||
|
||||
a.forEach((s) => {
|
||||
var kv = s.split('=');
|
||||
param[kv[0]] = kv[1];
|
||||
});
|
||||
return param;
|
||||
};
|
||||
|
||||
var param = getParam();
|
||||
|
||||
document.getElementById('html').className = param.theme;
|
|
@ -5,7 +5,6 @@
|
|||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=yes">
|
||||
<title>Anytype</title>
|
||||
|
||||
<script src="./common.js" type="text/javascript"></script>
|
||||
<script src="../../dist/js/jquery.js" type="text/javascript"></script>
|
||||
<link rel="stylesheet" href="./about.css" />
|
||||
</head>
|
||||
|
|
|
@ -40,7 +40,6 @@ class Api {
|
|||
languages: win.webContents.session.availableSpellCheckerLanguages,
|
||||
css: String(css || ''),
|
||||
});
|
||||
|
||||
win.route = '';
|
||||
};
|
||||
|
||||
|
|
177
electron/js/lib/installNativeMessagingHost.js
Normal file
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
|
||||
- This file is responsible for installing the native messaging host manifest file in the correct location for each browser on each platform.
|
||||
- It is idempotent, meaning it can run multiple times without causing any problems.
|
||||
- The native messaging host is a small executable that can be called by the Webclipper browser extension.
|
||||
- the executable remains in the anytype application files, but the manifest file is installed in each browser's unique nativeMessagingHost directory.
|
||||
- Read about what the actual executable does in the file: go/nativeMessagingHost.go
|
||||
|
||||
*/
|
||||
|
||||
const { existsSync, mkdir, writeFile } = require('fs');
|
||||
const { userInfo, homedir } = require('os');
|
||||
const { app } = require('electron');
|
||||
const path = require('path');
|
||||
const util = require('../util.js');
|
||||
const { fixPathForAsarUnpack, is } = require('electron-util');
|
||||
|
||||
const APP_NAME = 'com.anytype.desktop';
|
||||
const MANIFEST_FILENAME = `${APP_NAME}.json`;
|
||||
const EXTENSION_ID = 'jkmhmgghdjjbafmkgjmplhemjjnkligf';
|
||||
const USER_PATH = app.getPath('userData');
|
||||
const EXE_PATH = app.getPath('exe');
|
||||
|
||||
const getManifestPath = () => {
|
||||
const fn = `nativeMessagingHost${is.windows ? '.exe' : ''}`;
|
||||
return path.join(fixPathForAsarUnpack(__dirname), '..', '..', '..', 'dist', fn);
|
||||
};
|
||||
|
||||
const getHomeDir = () => {
|
||||
if (process.platform === 'darwin') {
|
||||
return userInfo().homedir;
|
||||
} else {
|
||||
return homedir();
|
||||
};
|
||||
};
|
||||
|
||||
const installNativeMessagingHost = () => {
|
||||
const { platform } = process;
|
||||
|
||||
// TODO make sure this is idempotent
|
||||
|
||||
const manifest = {
|
||||
name: APP_NAME,
|
||||
description: 'Anytype desktop <-> web clipper bridge',
|
||||
type: 'stdio',
|
||||
allowed_origins: [ `chrome-extension://${EXTENSION_ID}/` ],
|
||||
path: getManifestPath(),
|
||||
};
|
||||
|
||||
switch (platform) {
|
||||
case 'win32': {
|
||||
installToWindows(manifest);
|
||||
break;
|
||||
}
|
||||
case 'darwin': {
|
||||
installToMacOS(manifest);
|
||||
break;
|
||||
}
|
||||
case 'linux':
|
||||
installToLinux(manifest);
|
||||
break;
|
||||
default:
|
||||
console.log('unsupported platform: ', platform);
|
||||
break;
|
||||
};
|
||||
};
|
||||
|
||||
const installToMacOS = (manifest) => {
|
||||
const dirs = getDarwinDirectory();
|
||||
|
||||
for (const [ key, value ] of Object.entries(dirs)) {
|
||||
if (existsSync(value)) {
|
||||
const p = path.join(value, 'NativeMessagingHosts', MANIFEST_FILENAME);
|
||||
|
||||
writeManifest(p, manifest).catch(e => {
|
||||
console.log(`Error writing manifest for ${key}. ${e}`);
|
||||
});
|
||||
} else {
|
||||
console.log(`Warning: ${key} not found skipping.`);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const installToLinux = (manifest) => {
|
||||
const dir = `${getHomeDir()}/.config/google-chrome/`;
|
||||
|
||||
writeManifest(`${dir}NativeMessagingHosts/${MANIFEST_FILENAME}`, manifest);
|
||||
};
|
||||
|
||||
const installToWindows = (manifest) => {
|
||||
const dir = path.join(USER_PATH, 'browsers');
|
||||
|
||||
writeManifest(path.join(dir, 'chrome.json'), manifest);
|
||||
createWindowsRegistry(
|
||||
'HKCU\\SOFTWARE\\Google\\Chrome',
|
||||
`HKCU\\SOFTWARE\\Google\\Chrome\\NativeMessagingHosts\\${APP_NAME}`,
|
||||
path.join(dir, 'chrome.json')
|
||||
);
|
||||
};
|
||||
|
||||
const getRegeditInstance = () => {
|
||||
// eslint-disable-next-line
|
||||
const regedit = require('regedit');
|
||||
regedit.setExternalVBSLocation(
|
||||
path.join(path.dirname(EXE_PATH), 'resources/regedit/vbs')
|
||||
);
|
||||
return regedit;
|
||||
};
|
||||
|
||||
const createWindowsRegistry = async (check, location, jsonFile) => {
|
||||
const regedit = getRegeditInstance();
|
||||
const list = util.promisify(regedit.list);
|
||||
const createKey = util.promisify(regedit.createKey);
|
||||
const putValue = util.promisify(regedit.putValue);
|
||||
|
||||
console.log(`Adding registry: ${location}`);
|
||||
|
||||
// Check installed
|
||||
try {
|
||||
await list(check);
|
||||
} catch {
|
||||
console.log(`Not finding registry ${check} skipping.`);
|
||||
return;
|
||||
};
|
||||
|
||||
try {
|
||||
await createKey(location);
|
||||
|
||||
// Insert path to manifest
|
||||
const obj = {};
|
||||
obj[location] = {
|
||||
default: {
|
||||
value: jsonFile,
|
||||
type: 'REG_DEFAULT',
|
||||
},
|
||||
};
|
||||
|
||||
return putValue(obj);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
};
|
||||
};
|
||||
|
||||
const getDarwinDirectory = () => {
|
||||
const HOME_DIR = getHomeDir();
|
||||
/* eslint-disable no-useless-escape */
|
||||
return {
|
||||
Firefox: `${HOME_DIR}/Library/Application\ Support/Mozilla/`,
|
||||
Chrome: `${HOME_DIR}/Library/Application\ Support/Google/Chrome/`,
|
||||
'Chrome Beta': `${HOME_DIR}/Library/Application\ Support/Google/Chrome\ Beta/`,
|
||||
'Chrome Dev': `${HOME_DIR}/Library/Application\ Support/Google/Chrome\ Dev/`,
|
||||
'Chrome Canary': `${HOME_DIR}/Library/Application\ Support/Google/Chrome\ Canary/`,
|
||||
Chromium: `${HOME_DIR}/Library/Application\ Support/Chromium/`,
|
||||
'Microsoft Edge': `${HOME_DIR}/Library/Application\ Support/Microsoft\ Edge/`,
|
||||
'Microsoft Edge Beta': `${HOME_DIR}/Library/Application\ Support/Microsoft\ Edge\ Beta/`,
|
||||
'Microsoft Edge Dev': `${HOME_DIR}/Library/Application\ Support/Microsoft\ Edge\ Dev/`,
|
||||
'Microsoft Edge Canary': `${HOME_DIR}/Library/Application\ Support/Microsoft\ Edge\ Canary/`,
|
||||
Vivaldi: `${HOME_DIR}/Library/Application\ Support/Vivaldi/`,
|
||||
};
|
||||
/* eslint-enable no-useless-escape */
|
||||
};
|
||||
|
||||
const writeManifest = async (dst, data) => {
|
||||
if (!existsSync(path.dirname(dst))) {
|
||||
await mkdir(path.dirname(dst));
|
||||
};
|
||||
|
||||
await writeFile(dst, JSON.stringify(data, null, 2), (err) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
} else {
|
||||
console.log(`Manifest written: ${dst}`);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = { installNativeMessagingHost };
|
43
extension/entry.tsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import $ from 'jquery';
|
||||
import Popup from './popup';
|
||||
import Iframe from './iframe';
|
||||
import Util from './lib/util';
|
||||
import Extension from 'json/extension.json';
|
||||
import Url from 'json/url.json';
|
||||
|
||||
import './scss/common.scss';
|
||||
|
||||
window.Electron = {
|
||||
currentWindow: () => ({ windowId: 1 }),
|
||||
Api: () => {},
|
||||
};
|
||||
window.AnytypeGlobalConfig = { emojiPrefix: Url.emojiPrefix, menuBorderTop: 16, menuBorderBottom: 16, debug: { mw: true } };
|
||||
|
||||
let rootId = '';
|
||||
let component: any = null;
|
||||
|
||||
if (Util.isPopup()) {
|
||||
rootId = `${Extension.clipper.prefix}-popup`;
|
||||
component = <Popup />;
|
||||
} else
|
||||
if (Util.isIframe()) {
|
||||
rootId = `${Extension.clipper.prefix}-iframe`;
|
||||
component = <Iframe />;
|
||||
};
|
||||
|
||||
if (!rootId) {
|
||||
console.error('[Entry] rootId is not defined');
|
||||
} else {
|
||||
const html = $('html');
|
||||
const body = $('body');
|
||||
const root = $(`<div id="${rootId}"></div>`);
|
||||
|
||||
if (!$(`#${rootId}`).length) {
|
||||
body.append(root);
|
||||
html.addClass(rootId);
|
||||
};
|
||||
|
||||
ReactDOM.render(component, root.get(0));
|
||||
};
|
134
extension/iframe.tsx
Normal file
|
@ -0,0 +1,134 @@
|
|||
import * as React from 'react';
|
||||
import * as hs from 'history';
|
||||
import $ from 'jquery';
|
||||
import { Router, Route, Switch } from 'react-router-dom';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { Provider } from 'mobx-react';
|
||||
import { configure } from 'mobx';
|
||||
import { ListMenu } from 'Component';
|
||||
import { dispatcher, C, UtilCommon, UtilRouter } from 'Lib';
|
||||
import { commonStore, authStore, blockStore, detailStore, dbStore, menuStore, popupStore, extensionStore } from 'Store';
|
||||
|
||||
import Index from './iframe/index';
|
||||
import Create from './iframe/create';
|
||||
import Util from './lib/util';
|
||||
|
||||
require('./scss/iframe.scss');
|
||||
|
||||
configure({ enforceActions: 'never' });
|
||||
|
||||
const Routes = [
|
||||
{ path: '/' },
|
||||
{ path: '/:page' },
|
||||
];
|
||||
|
||||
const Components = {
|
||||
index: Index,
|
||||
create: Create,
|
||||
};
|
||||
|
||||
const memoryHistory = hs.createMemoryHistory;
|
||||
const history = memoryHistory();
|
||||
|
||||
const rootStore = {
|
||||
commonStore,
|
||||
authStore,
|
||||
blockStore,
|
||||
detailStore,
|
||||
dbStore,
|
||||
menuStore,
|
||||
popupStore,
|
||||
extensionStore,
|
||||
};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
Electron: any;
|
||||
$: any;
|
||||
Anytype: any;
|
||||
isWebVersion: boolean;
|
||||
AnytypeGlobalConfig: any;
|
||||
}
|
||||
};
|
||||
|
||||
window.$ = $;
|
||||
window.$ = $;
|
||||
window.Anytype = {
|
||||
Store: rootStore,
|
||||
Lib: {
|
||||
C,
|
||||
UtilCommon,
|
||||
dispatcher,
|
||||
Storage,
|
||||
},
|
||||
};
|
||||
|
||||
class RoutePage extends React.Component<RouteComponentProps> {
|
||||
render () {
|
||||
const { match } = this.props;
|
||||
const params = match.params as any;
|
||||
const page = params.page || 'index';
|
||||
const Component = Components[page];
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ListMenu key="listMenu" {...this.props} />
|
||||
|
||||
{Component ? <Component /> : null}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
class Iframe extends React.Component {
|
||||
|
||||
node: any = null;
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Router history={history}>
|
||||
<Provider {...rootStore}>
|
||||
<div ref={node => this.node = node}>
|
||||
<Switch>
|
||||
{Routes.map((item: any, i: number) => (
|
||||
<Route path={item.path} exact={true} key={i} component={RoutePage} />
|
||||
))}
|
||||
</Switch>
|
||||
</div>
|
||||
</Provider>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
UtilRouter.init(history);
|
||||
|
||||
/* @ts-ignore */
|
||||
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
console.log('[Iframe]', msg, sender);
|
||||
|
||||
switch (msg.type) {
|
||||
case 'init':
|
||||
const { appKey, gatewayPort, serverPort } = msg;
|
||||
|
||||
Util.init(serverPort, gatewayPort);
|
||||
Util.authorize(appKey, () => UtilRouter.go('/create', {}));
|
||||
|
||||
sendResponse({});
|
||||
break;
|
||||
|
||||
case 'clickMenu': {
|
||||
extensionStore.setHtml(msg.html);
|
||||
|
||||
sendResponse({});
|
||||
break;
|
||||
};
|
||||
|
||||
};
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
export default Iframe;
|
185
extension/iframe/create.tsx
Normal file
|
@ -0,0 +1,185 @@
|
|||
import * as React from 'react';
|
||||
import $ from 'jquery';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Button, Block, Loader, Icon, Select } from 'Component';
|
||||
import { I, C, M, translate, UtilObject, UtilData } from 'Lib';
|
||||
import { blockStore, extensionStore, menuStore, dbStore, commonStore } from 'Store';
|
||||
|
||||
interface State {
|
||||
isLoading: boolean;
|
||||
error: string;
|
||||
object: any;
|
||||
};
|
||||
|
||||
const ROOT_ID = 'preview';
|
||||
|
||||
const Create = observer(class Create extends React.Component<I.PageComponent, State> {
|
||||
|
||||
state: State = {
|
||||
isLoading: false,
|
||||
error: '',
|
||||
object: null,
|
||||
};
|
||||
node: any = null;
|
||||
refSpace: any = null;
|
||||
html = '';
|
||||
|
||||
constructor (props: I.PageComponent) {
|
||||
super(props);
|
||||
|
||||
this.onSave = this.onSave.bind(this);
|
||||
this.onClose = this.onClose.bind(this);
|
||||
this.onSelect = this.onSelect.bind(this);
|
||||
this.onSpaceChange = this.onSpaceChange.bind(this);
|
||||
};
|
||||
|
||||
render () {
|
||||
const { isLoading, error, object } = this.state;
|
||||
const { html } = extensionStore;
|
||||
const { space } = commonStore;
|
||||
const children = blockStore.getChildren(ROOT_ID, ROOT_ID);
|
||||
|
||||
return (
|
||||
<div ref={ref => this.node = ref} className="page pageCreate">
|
||||
{isLoading ? <Loader type="loader" /> : ''}
|
||||
|
||||
<div className="head">
|
||||
<div className="side left">
|
||||
<Select
|
||||
id="select-space"
|
||||
ref={ref => this.refSpace = ref}
|
||||
value=""
|
||||
options={[]}
|
||||
onChange={this.onSpaceChange}
|
||||
menuParam={{
|
||||
horizontal: I.MenuDirection.Center,
|
||||
data: { maxHeight: 360 }
|
||||
}}
|
||||
/>
|
||||
|
||||
<div id="select" className="select" onMouseDown={this.onSelect}>
|
||||
<div className="item">
|
||||
<div className="name">{object ? object.name : translate('commonSelectObject')}</div>
|
||||
</div>
|
||||
<Icon className="arrow light" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="side right">
|
||||
<Button text="Cancel" color="blank" className="c32" onClick={this.onClose} />
|
||||
<Button text="Save" color="pink" className="c32" onClick={this.onSave} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="blocks">
|
||||
{children.map((block: I.Block, i: number) => (
|
||||
<Block
|
||||
key={block.id}
|
||||
{...this.props}
|
||||
rootId={ROOT_ID}
|
||||
index={i}
|
||||
block={block}
|
||||
getWrapperWidth={() => this.getWrapperWidth()}
|
||||
readonly={true}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
const spaces = dbStore.getSpaces().map(it => ({ ...it, id: it.targetSpaceId, object: it, iconSize: 16 })).filter(it => it);
|
||||
|
||||
if (this.refSpace && spaces.length) {
|
||||
const space = commonStore.space || spaces[0].targetSpaceId;
|
||||
|
||||
this.refSpace?.setOptions(spaces);
|
||||
this.refSpace?.setValue(space);
|
||||
this.onSpaceChange(space);
|
||||
};
|
||||
};
|
||||
|
||||
componentDidUpdate (): void {
|
||||
this.initBlocks();
|
||||
};
|
||||
|
||||
initBlocks () {
|
||||
const { html } = extensionStore;
|
||||
|
||||
if (!html || (html == this.html)) {
|
||||
return;
|
||||
};
|
||||
|
||||
this.html = html;
|
||||
|
||||
C.BlockPreview(html, (message: any) => {
|
||||
if (message.error.code) {
|
||||
return;
|
||||
};
|
||||
|
||||
const structure: any[] = [];
|
||||
const blocks = message.blocks.map(it => new M.Block(it));
|
||||
|
||||
blocks.forEach((block: any) => {
|
||||
structure.push({ id: block.id, childrenIds: block.childrenIds });
|
||||
});
|
||||
|
||||
blockStore.set(ROOT_ID, blocks);
|
||||
blockStore.setStructure(ROOT_ID, structure);
|
||||
|
||||
this.forceUpdate();
|
||||
});
|
||||
};
|
||||
|
||||
onSelect () {
|
||||
const { object } = this.state;
|
||||
const node = $(this.node);
|
||||
const filters: I.Filter[] = [
|
||||
{ operator: I.FilterOperator.And, relationKey: 'layout', condition: I.FilterCondition.In, value: UtilObject.getPageLayouts() },
|
||||
];
|
||||
|
||||
menuStore.open('searchObject', {
|
||||
element: node.find('#select'),
|
||||
data: {
|
||||
value: object ? [ object.id ] : [],
|
||||
canAdd: true,
|
||||
filters,
|
||||
details: { origin: I.ObjectOrigin.Webclipper },
|
||||
dataMapper: item => ({ ...item, iconSize: 16 }),
|
||||
onSelect: (item) => {
|
||||
this.setState({ object: item });
|
||||
},
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onSpaceChange (id: string): void {
|
||||
commonStore.spaceSet(id);
|
||||
UtilData.createsSubscriptions();
|
||||
};
|
||||
|
||||
getWrapperWidth () {
|
||||
const win: any = $(window);
|
||||
return win.width() - 96;
|
||||
};
|
||||
|
||||
onSave () {
|
||||
const { object } = this.state;
|
||||
|
||||
if (!object) {
|
||||
return;
|
||||
};
|
||||
|
||||
C.BlockPaste (object.id, '', { from: 0, to: 0 }, [], false, { html: this.html }, () => {
|
||||
this.onClose();
|
||||
});
|
||||
};
|
||||
|
||||
onClose () {
|
||||
this.html = '';
|
||||
parent.postMessage({ type: 'clickClose' }, '*');
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
export default Create;
|
46
extension/iframe/index.tsx
Normal file
|
@ -0,0 +1,46 @@
|
|||
import * as React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { I, UtilRouter, Storage } from 'Lib';
|
||||
import Util from '../lib/util';
|
||||
|
||||
const Index = observer(class Index extends React.Component<I.PageComponent> {
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className="page pageIndex" />
|
||||
);
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
this.getPorts();
|
||||
};
|
||||
|
||||
getPorts (onError?: () => void): void {
|
||||
Util.sendMessage({ type: 'checkPorts' }, response => {
|
||||
console.log('[Iframe] checkPorts', response);
|
||||
|
||||
if (!response.ports || !response.ports.length) {
|
||||
this.setState({ error: 'Automatic pairing failed, please open the app' });
|
||||
|
||||
if (onError) {
|
||||
onError();
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
Util.init(response.ports[1], response.ports[2]);
|
||||
this.login();
|
||||
});
|
||||
};
|
||||
|
||||
login () {
|
||||
const appKey = Storage.get('appKey');
|
||||
|
||||
if (appKey) {
|
||||
Util.authorize(appKey, () => UtilRouter.go('/create', {}), () => Storage.delete('appKey'));
|
||||
};
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
export default Index;
|
78
extension/lib/util.ts
Normal file
|
@ -0,0 +1,78 @@
|
|||
import { UtilData, dispatcher } from 'Lib';
|
||||
import { authStore, commonStore, extensionStore } from 'Store';
|
||||
import Extension from 'json/extension.json';
|
||||
|
||||
const INDEX_POPUP = '/popup/index.html';
|
||||
const INDEX_IFRAME = '/iframe/index.html'
|
||||
|
||||
class Util {
|
||||
|
||||
extensionId () {
|
||||
return Extension.clipper.id;
|
||||
};
|
||||
|
||||
isExtension () {
|
||||
return (
|
||||
(location.protocol == 'chrome-extension:') &&
|
||||
(location.hostname == this.extensionId())
|
||||
);
|
||||
};
|
||||
|
||||
isPopup () {
|
||||
return this.isExtension() && (location.pathname == INDEX_POPUP);
|
||||
};
|
||||
|
||||
isIframe () {
|
||||
return this.isExtension() && (location.pathname == INDEX_IFRAME);
|
||||
};
|
||||
|
||||
fromPopup (url: string) {
|
||||
return url.match(INDEX_POPUP);
|
||||
};
|
||||
|
||||
fromIframe (url: string) {
|
||||
return url.match(INDEX_IFRAME);
|
||||
};
|
||||
|
||||
sendMessage (msg: any, callBack: (response) => void) {
|
||||
/* @ts-ignore */
|
||||
chrome.runtime.sendMessage(msg, callBack);
|
||||
};
|
||||
|
||||
getCurrentTab (callBack: (tab) => void) {
|
||||
/* @ts-ignore */
|
||||
chrome.tabs.query({ active: true, lastFocusedWindow: true }, tabs => callBack(tabs[0]));
|
||||
};
|
||||
|
||||
init (serverPort: string, gatewayPort: string) {
|
||||
extensionStore.serverPort = serverPort;
|
||||
extensionStore.gatewayPort = gatewayPort;
|
||||
|
||||
dispatcher.init(`http://127.0.0.1:${serverPort}`);
|
||||
commonStore.gatewaySet(`http://127.0.0.1:${gatewayPort}`);
|
||||
};
|
||||
|
||||
authorize (appKey: string, onSuccess?: () => void, onError?: (error) => void) {
|
||||
const { serverPort, gatewayPort } = extensionStore;
|
||||
|
||||
authStore.appKeySet(appKey);
|
||||
UtilData.createSession((message: any) => {
|
||||
if (message.error.code) {
|
||||
if (onError) {
|
||||
onError(message.error);
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
this.sendMessage({ type: 'init', appKey, serverPort, gatewayPort }, () => {});
|
||||
UtilData.createsSubscriptions(onSuccess);
|
||||
});
|
||||
};
|
||||
|
||||
optionMapper (it: any) {
|
||||
return it._empty_ ? null : { ...it, object: it };
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
export default new Util();
|
128
extension/popup.tsx
Normal file
|
@ -0,0 +1,128 @@
|
|||
import * as React from 'react';
|
||||
import * as hs from 'history';
|
||||
import $ from 'jquery';
|
||||
import { Router, Route, Switch } from 'react-router-dom';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { Provider } from 'mobx-react';
|
||||
import { configure } from 'mobx';
|
||||
import { ListMenu } from 'Component';
|
||||
import { dispatcher, C, UtilCommon, UtilRouter } from 'Lib';
|
||||
import { commonStore, authStore, blockStore, detailStore, dbStore, menuStore, popupStore, extensionStore } from 'Store';
|
||||
import Extension from 'json/extension.json';
|
||||
|
||||
import Index from './popup/index';
|
||||
import Challenge from './popup/challenge';
|
||||
import Create from './popup/create';
|
||||
import Success from './popup/success';
|
||||
|
||||
import './scss/popup.scss';
|
||||
|
||||
configure({ enforceActions: 'never' });
|
||||
|
||||
const Routes = [
|
||||
{ path: '/' },
|
||||
{ path: '/:page' },
|
||||
];
|
||||
|
||||
const Components = {
|
||||
index: Index,
|
||||
challenge: Challenge,
|
||||
create: Create,
|
||||
success: Success,
|
||||
};
|
||||
|
||||
const memoryHistory = hs.createMemoryHistory;
|
||||
const history = memoryHistory();
|
||||
|
||||
const rootStore = {
|
||||
commonStore,
|
||||
authStore,
|
||||
blockStore,
|
||||
detailStore,
|
||||
dbStore,
|
||||
menuStore,
|
||||
popupStore,
|
||||
extensionStore,
|
||||
};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
Electron: any;
|
||||
$: any;
|
||||
Anytype: any;
|
||||
isWebVersion: boolean;
|
||||
AnytypeGlobalConfig: any;
|
||||
}
|
||||
};
|
||||
|
||||
window.$ = $;
|
||||
window.Anytype = {
|
||||
Store: rootStore,
|
||||
Lib: {
|
||||
C,
|
||||
UtilCommon,
|
||||
dispatcher,
|
||||
Storage,
|
||||
},
|
||||
};
|
||||
|
||||
class RoutePage extends React.Component<RouteComponentProps> {
|
||||
render () {
|
||||
const { match } = this.props;
|
||||
const params = match.params as any;
|
||||
const page = params.page || 'index';
|
||||
const Component = Components[page];
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ListMenu key="listMenu" {...this.props} />
|
||||
|
||||
{Component ? <Component /> : null}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
class Popup extends React.Component {
|
||||
|
||||
node: any = null;
|
||||
|
||||
constructor (props: any) {
|
||||
super(props);
|
||||
};
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Router history={history}>
|
||||
<Provider {...rootStore}>
|
||||
<div ref={node => this.node = node}>
|
||||
<Switch>
|
||||
{Routes.map((item: any, i: number) => (
|
||||
<Route path={item.path} exact={true} key={i} component={RoutePage} />
|
||||
))}
|
||||
</Switch>
|
||||
</div>
|
||||
</Provider>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
UtilRouter.init(history);
|
||||
|
||||
/* @ts-ignore */
|
||||
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
console.log('[Popup]', msg, sender);
|
||||
|
||||
if (sender.id != Extension.clipper.id) {
|
||||
return false;
|
||||
};
|
||||
|
||||
//sendResponse({ type: msg.type, ref: 'popup' });
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
export default Popup;
|
57
extension/popup/challenge.tsx
Normal file
|
@ -0,0 +1,57 @@
|
|||
import * as React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Button, Input, Error } from 'Component';
|
||||
import { I, C, Storage, UtilRouter } from 'Lib';
|
||||
import { extensionStore } from 'Store';
|
||||
import Util from '../lib/util';
|
||||
|
||||
interface State {
|
||||
error: string;
|
||||
};
|
||||
|
||||
const Challenge = observer(class Challenge extends React.Component<I.PageComponent, State> {
|
||||
|
||||
ref: any = null;
|
||||
state = {
|
||||
error: '',
|
||||
};
|
||||
|
||||
constructor (props: I.PageComponent) {
|
||||
super(props);
|
||||
|
||||
this.onSubmit = this.onSubmit.bind(this);
|
||||
};
|
||||
|
||||
render () {
|
||||
const { error } = this.state;
|
||||
|
||||
return (
|
||||
<form className="page pageChallenge" onSubmit={this.onSubmit}>
|
||||
<Input ref={ref => this.ref = ref} placeholder="Challenge" />
|
||||
|
||||
<div className="buttons">
|
||||
<Button type="input" color="pink" className="c32" text="Authorize" />
|
||||
</div>
|
||||
|
||||
<Error text={error} />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
onSubmit (e: any) {
|
||||
e.preventDefault();
|
||||
|
||||
C.AccountLocalLinkSolveChallenge(extensionStore.challengeId, this.ref?.getValue().trim(), (message: any) => {
|
||||
if (message.error.code) {
|
||||
this.setState({ error: message.error.description });
|
||||
return;
|
||||
};
|
||||
|
||||
Storage.set('appKey', message.appKey);
|
||||
Util.authorize(message.appKey, () => UtilRouter.go('/create', {}));
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
export default Challenge;
|
453
extension/popup/create.tsx
Normal file
|
@ -0,0 +1,453 @@
|
|||
import * as React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { observable } from 'mobx';
|
||||
import arrayMove from 'array-move';
|
||||
import { getRange, setRange } from 'selection-ranges';
|
||||
import { Label, Input, Button, Select, Loader, Error, DragBox, Tag, Textarea } from 'Component';
|
||||
import { I, C, UtilCommon, UtilData, Relation, keyboard, UtilObject, UtilRouter } from 'Lib';
|
||||
import { dbStore, detailStore, commonStore, menuStore, extensionStore } from 'Store';
|
||||
import Constant from 'json/constant.json';
|
||||
import Util from '../lib/util';
|
||||
|
||||
interface State {
|
||||
error: string;
|
||||
isLoading: boolean;
|
||||
};
|
||||
|
||||
const MAX_LENGTH = 320;
|
||||
|
||||
const Create = observer(class Create extends React.Component<I.PageComponent, State> {
|
||||
|
||||
details: any = {
|
||||
type: '',
|
||||
tag: [],
|
||||
};
|
||||
|
||||
node: any = null;
|
||||
refName: any = null;
|
||||
refSpace: any = null;
|
||||
refType: any = null;
|
||||
refComment: any = null;
|
||||
isCreating = false;
|
||||
url = '';
|
||||
|
||||
state = {
|
||||
isLoading: false,
|
||||
error: '',
|
||||
};
|
||||
|
||||
constructor (props: I.PageComponent) {
|
||||
super(props);
|
||||
|
||||
this.onSubmit = this.onSubmit.bind(this);
|
||||
this.onSpaceChange = this.onSpaceChange.bind(this);
|
||||
this.onTypeChange = this.onTypeChange.bind(this);
|
||||
this.onKeyPress = this.onKeyPress.bind(this);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
this.onKeyUp = this.onKeyUp.bind(this);
|
||||
this.onInput = this.onInput.bind(this);
|
||||
this.onFocus = this.onFocus.bind(this);
|
||||
this.onBlur = this.onBlur.bind(this);
|
||||
this.onDragEnd = this.onDragEnd.bind(this);
|
||||
this.focus = this.focus.bind(this);
|
||||
};
|
||||
|
||||
render () {
|
||||
const { isLoading, error } = this.state;
|
||||
const { space } = commonStore;
|
||||
const tags = this.getTagsValue();
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref => this.node = ref}
|
||||
className="page pageCreate"
|
||||
>
|
||||
{isLoading ? <Loader type="loader" /> : ''}
|
||||
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<div className="rows">
|
||||
<div className="row">
|
||||
<Label text="Title" />
|
||||
<Input ref={ref => this.refName = ref} />
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<Label text="Space" />
|
||||
<Select
|
||||
id="select-space"
|
||||
ref={ref => this.refSpace = ref}
|
||||
value=""
|
||||
options={[]}
|
||||
onChange={this.onSpaceChange}
|
||||
menuParam={{
|
||||
horizontal: I.MenuDirection.Center,
|
||||
data: { maxHeight: 180 }
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<Label text="Save as" />
|
||||
<Select
|
||||
id="select-type"
|
||||
ref={ref => this.refType = ref}
|
||||
readonly={!space}
|
||||
value=""
|
||||
options={[]}
|
||||
onChange={this.onTypeChange}
|
||||
menuParam={{
|
||||
horizontal: I.MenuDirection.Center,
|
||||
data: { maxHeight: 180 }
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<Label text="Tag" />
|
||||
|
||||
<div id="select-tag" className="box cell isEditing c-select" onClick={this.focus}>
|
||||
<div className="value cellContent c-select">
|
||||
<span id="list">
|
||||
<DragBox onDragEnd={this.onDragEnd}>
|
||||
{tags.map((item: any, i: number) => (
|
||||
<span
|
||||
key={i}
|
||||
id={`item-${item.id}`}
|
||||
className="itemWrap isDraggable"
|
||||
draggable={true}
|
||||
{...UtilCommon.dataProps({ id: item.id, index: i })}
|
||||
>
|
||||
<Tag
|
||||
key={item.id}
|
||||
text={item.name}
|
||||
color={item.color}
|
||||
canEdit={true}
|
||||
className={Relation.selectClassName(I.RelationType.MultiSelect)}
|
||||
onRemove={() => this.onValueRemove(item.id)}
|
||||
/>
|
||||
</span>
|
||||
))}
|
||||
</DragBox>
|
||||
</span>
|
||||
|
||||
<span className="entryWrap">
|
||||
<span
|
||||
id="entry"
|
||||
contentEditable={true}
|
||||
suppressContentEditableWarning={true}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
onInput={this.onInput}
|
||||
onKeyDown={this.onKeyDown}
|
||||
onKeyUp={this.onKeyUp}
|
||||
>
|
||||
{'\n'}
|
||||
</span>
|
||||
<div id="placeholder" className="placeholder">Type...</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="buttons">
|
||||
<Button color="pink" className="c32" text="Save" type="input" subType="submit" onClick={this.onSubmit} />
|
||||
</div>
|
||||
|
||||
<Error text={error} />
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
this.initSpace();
|
||||
this.initName();
|
||||
this.initType();
|
||||
};
|
||||
|
||||
componentDidUpdate(): void {
|
||||
this.initType();
|
||||
};
|
||||
|
||||
initSpace () {
|
||||
const spaces = this.getSpaces();
|
||||
|
||||
if (!this.refSpace || !spaces.length) {
|
||||
return;
|
||||
};
|
||||
|
||||
const space = commonStore.space || spaces[0].targetSpaceId;
|
||||
|
||||
this.refSpace.setOptions(spaces);
|
||||
this.refSpace.setValue(space);
|
||||
this.onSpaceChange(space);
|
||||
};
|
||||
|
||||
initType () {
|
||||
const options = this.getTypes().map(it => ({ ...it, id: it.uniqueKey }));
|
||||
|
||||
if (!this.refType || !options.length) {
|
||||
return;
|
||||
};
|
||||
|
||||
this.details.type = this.details.type || options[0].id;
|
||||
|
||||
this.refType.setOptions(options);
|
||||
this.refType.setValue(this.details.type);
|
||||
};
|
||||
|
||||
initName () {
|
||||
if (!this.refName) {
|
||||
return;
|
||||
};
|
||||
|
||||
Util.getCurrentTab(tab => {
|
||||
if (!tab) {
|
||||
return;
|
||||
};
|
||||
|
||||
this.refName.setValue(tab.title);
|
||||
this.refName.focus();
|
||||
|
||||
this.url = tab.url;
|
||||
});
|
||||
};
|
||||
|
||||
getObjects (subId: string) {
|
||||
return dbStore.getRecords(subId, '').map(id => detailStore.get(subId, id));
|
||||
};
|
||||
|
||||
getSpaces () {
|
||||
return dbStore.getSpaces().map(it => ({ ...it, id: it.targetSpaceId, object: it })).filter(it => it)
|
||||
};
|
||||
|
||||
getTypes () {
|
||||
const layouts = UtilObject.getPageLayouts();
|
||||
return this.getObjects(Constant.subId.type).
|
||||
map(Util.optionMapper).
|
||||
filter(this.filter).
|
||||
filter(it => layouts.includes(it.recommendedLayout) && (it.spaceId == commonStore.space)).
|
||||
sort(UtilData.sortByName);
|
||||
};
|
||||
|
||||
filter (it: any) {
|
||||
return it && !it.isHidden && !it.isArchived && !it.isDeleted;
|
||||
};
|
||||
|
||||
onTypeChange (id: string): void {
|
||||
this.details.type = id;
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
onSpaceChange (id: string): void {
|
||||
commonStore.spaceSet(id);
|
||||
UtilData.createsSubscriptions(() => this.forceUpdate());
|
||||
};
|
||||
|
||||
getTagsValue () {
|
||||
return dbStore.getRecords(Constant.subId.option, '').
|
||||
filter(id => this.details.tag.includes(id)).
|
||||
map(id => detailStore.get(Constant.subId.option, id)).
|
||||
filter(it => it && !it._empty_);
|
||||
};
|
||||
|
||||
clear () {
|
||||
const node = $(this.node);
|
||||
node.find('#entry').text(' ');
|
||||
|
||||
this.focus();
|
||||
};
|
||||
|
||||
focus () {
|
||||
const node = $(this.node);
|
||||
const entry = node.find('#entry');
|
||||
|
||||
if (entry.length) {
|
||||
window.setTimeout(() => {
|
||||
entry.focus();
|
||||
setRange(entry.get(0), { start: 0, end: 0 });
|
||||
|
||||
this.scrollToBottom();
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
onValueRemove (id: string) {
|
||||
this.setValue(this.details.tag.filter(it => it != id));
|
||||
};
|
||||
|
||||
onDragEnd (oldIndex: number, newIndex: number) {
|
||||
this.setValue(arrayMove(this.details.tag, oldIndex, newIndex));
|
||||
};
|
||||
|
||||
onKeyDown (e: any) {
|
||||
const node = $(this.node);
|
||||
const entry = node.find('#entry');
|
||||
|
||||
keyboard.shortcut('backspace', e, (pressed: string) => {
|
||||
e.stopPropagation();
|
||||
|
||||
const range = getRange(entry.get(0));
|
||||
if (range.start || range.end) {
|
||||
return;
|
||||
};
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const value = this.getValue();
|
||||
value.existing.pop();
|
||||
this.setValue(value.existing);
|
||||
});
|
||||
|
||||
this.placeholderCheck();
|
||||
this.scrollToBottom();
|
||||
};
|
||||
|
||||
onKeyPress (e: any) {
|
||||
const node = $(this.node);
|
||||
const entry = node.find('#entry');
|
||||
|
||||
if (entry.length && (entry.text().length >= MAX_LENGTH)) {
|
||||
e.preventDefault();
|
||||
};
|
||||
};
|
||||
|
||||
onKeyUp (e: any) {
|
||||
menuStore.updateData('dataviewOptionList', { filter: this.getValue().new });
|
||||
|
||||
this.placeholderCheck();
|
||||
this.resize();
|
||||
this.scrollToBottom();
|
||||
};
|
||||
|
||||
onInput () {
|
||||
this.placeholderCheck();
|
||||
};
|
||||
|
||||
onFocus () {
|
||||
const relation = dbStore.getRelationByKey('tag');
|
||||
const element = '#select-tag';
|
||||
|
||||
menuStore.open('dataviewOptionList', {
|
||||
element,
|
||||
horizontal: I.MenuDirection.Center,
|
||||
commonFilter: true,
|
||||
onOpen: () => {
|
||||
window.setTimeout(() => { $(element).addClass('isFocused'); });
|
||||
},
|
||||
onClose: () => { $(element).removeClass('isFocused'); },
|
||||
data: {
|
||||
canAdd: true,
|
||||
filter: '',
|
||||
value: this.details.tag,
|
||||
maxCount: relation.maxCount,
|
||||
noFilter: true,
|
||||
relation: observable.box(relation),
|
||||
maxHeight: 120,
|
||||
onChange: (value: string[]) => {
|
||||
this.setValue(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onBlur () {
|
||||
};
|
||||
|
||||
placeholderCheck () {
|
||||
const node = $(this.node);
|
||||
const value = this.getValue();
|
||||
const list = node.find('#list');
|
||||
const placeholder = node.find('#placeholder');
|
||||
|
||||
if (value.existing.length) {
|
||||
list.show();
|
||||
} else {
|
||||
list.hide();
|
||||
};
|
||||
|
||||
if (value.new || value.existing.length) {
|
||||
placeholder.hide();
|
||||
} else {
|
||||
placeholder.show();
|
||||
};
|
||||
};
|
||||
|
||||
getValue () {
|
||||
const node = $(this.node);
|
||||
const list = node.find('#list');
|
||||
const items = list.find('.itemWrap');
|
||||
const entry = node.find('#entry');
|
||||
const existing: any[] = [];
|
||||
|
||||
items.each((i: number, item: any) => {
|
||||
item = $(item);
|
||||
existing.push(item.data('id'));
|
||||
});
|
||||
|
||||
return {
|
||||
existing,
|
||||
new: (entry.length ? String(entry.text() || '').trim() : ''),
|
||||
};
|
||||
};
|
||||
|
||||
setValue (value: string[]) {
|
||||
const relation = dbStore.getRelationByKey('tag');
|
||||
|
||||
value = UtilCommon.arrayUnique(value);
|
||||
|
||||
const length = value.length;
|
||||
if (relation.maxCount && (length > relation.maxCount)) {
|
||||
value = value.slice(length - relation.maxCount, length);
|
||||
};
|
||||
|
||||
this.details.tag = value;
|
||||
this.clear();
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
onSubmit (e: any) {
|
||||
e.preventDefault();
|
||||
|
||||
if (this.isCreating) {
|
||||
return;
|
||||
};
|
||||
|
||||
this.isCreating = true;
|
||||
this.setState({ isLoading: true, error: '' });
|
||||
|
||||
const details = Object.assign({ name: this.refName?.getValue(), origin: I.ObjectOrigin.Webclipper }, this.details);
|
||||
const type = details.type;
|
||||
|
||||
delete(details.type);
|
||||
|
||||
C.ObjectCreateFromUrl(details, commonStore.space, type, this.url, (message: any) => {
|
||||
this.setState({ isLoading: false });
|
||||
|
||||
if (message.error.code) {
|
||||
this.setState({ error: message.error.description });
|
||||
} else {
|
||||
extensionStore.createdObject = message.details;
|
||||
|
||||
UtilRouter.go('/success', {});
|
||||
};
|
||||
|
||||
this.isCreating = false;
|
||||
});
|
||||
};
|
||||
|
||||
scrollToBottom () {
|
||||
const node = $(this.node);
|
||||
const content: any = node.find('.cellContent');
|
||||
|
||||
content.scrollTop(content.get(0).scrollHeight + parseInt(content.css('paddingBottom')));
|
||||
};
|
||||
|
||||
resize () {
|
||||
$(window).trigger('resize.menuDataviewOptionList');
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
export default Create;
|
126
extension/popup/index.tsx
Normal file
|
@ -0,0 +1,126 @@
|
|||
import * as React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Label, Button, Error } from 'Component';
|
||||
import { I, C, UtilRouter, Storage } from 'Lib';
|
||||
import { extensionStore } from 'Store';
|
||||
import Url from 'json/url.json';
|
||||
|
||||
import Util from '../lib/util';
|
||||
|
||||
interface State {
|
||||
error: string;
|
||||
};
|
||||
|
||||
const Index = observer(class Index extends React.Component<I.PageComponent, State> {
|
||||
|
||||
state = {
|
||||
error: '',
|
||||
};
|
||||
interval: any = 0;
|
||||
|
||||
constructor (props: I.PageComponent) {
|
||||
super(props);
|
||||
|
||||
this.onOpen = this.onOpen.bind(this);
|
||||
this.onDownload = this.onDownload.bind(this);
|
||||
};
|
||||
|
||||
render () {
|
||||
const { error } = this.state;
|
||||
|
||||
return (
|
||||
<div className="page pageIndex">
|
||||
<Label text="To save in Anytype you need to Pair with the app" />
|
||||
|
||||
<div className="buttons">
|
||||
<Button color="pink" className="c32" text="Pair with app" onClick={this.onOpen} />
|
||||
<Button color="blank" className="c32" text="Download app" onClick={this.onDownload} />
|
||||
</div>
|
||||
|
||||
<Error text={error} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
this.checkPorts();
|
||||
};
|
||||
|
||||
checkPorts (onError?: () => void): void {
|
||||
Util.sendMessage({ type: 'getPorts' }, response => {
|
||||
Util.sendMessage({ type: 'checkPorts' }, response => {
|
||||
console.log('[Popup] checkPorts', response);
|
||||
|
||||
if (!response.ports || !response.ports.length) {
|
||||
this.setState({ error: 'Automatic pairing failed, please open the app' });
|
||||
|
||||
if (onError) {
|
||||
onError();
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
Util.init(response.ports[1], response.ports[2]);
|
||||
this.login();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
login () {
|
||||
const appKey = Storage.get('appKey');
|
||||
|
||||
if (appKey) {
|
||||
Util.authorize(appKey, () => UtilRouter.go('/create', {}), () => {
|
||||
Storage.delete('appKey');
|
||||
this.login();
|
||||
});
|
||||
} else {
|
||||
/* @ts-ignore */
|
||||
const manifest = chrome.runtime.getManifest();
|
||||
|
||||
C.AccountLocalLinkNewChallenge(manifest.name, (message: any) => {
|
||||
if (message.error.code) {
|
||||
this.setState({ error: message.error.description });
|
||||
return;
|
||||
};
|
||||
|
||||
extensionStore.challengeId = message.challengeId;
|
||||
UtilRouter.go('/challenge', {});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
onOpen () {
|
||||
const { serverPort, gatewayPort } = extensionStore;
|
||||
|
||||
if (serverPort && gatewayPort) {
|
||||
this.login();
|
||||
return;
|
||||
};
|
||||
|
||||
let cnt = 0;
|
||||
|
||||
Util.sendMessage({ type: 'launchApp' }, response => {
|
||||
this.interval = setInterval(() => {
|
||||
this.checkPorts(() => {
|
||||
cnt++;
|
||||
|
||||
if (cnt >= 30) {
|
||||
this.setState({ error: 'App open failed' });
|
||||
|
||||
clearInterval(this.interval);
|
||||
console.log('App open try', cnt);
|
||||
};
|
||||
});
|
||||
}, 1000);
|
||||
});
|
||||
};
|
||||
|
||||
onDownload () {
|
||||
window.open(Url.download);
|
||||
};
|
||||
|
||||
|
||||
});
|
||||
|
||||
export default Index;
|
47
extension/popup/success.tsx
Normal file
|
@ -0,0 +1,47 @@
|
|||
import * as React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Button } from 'Component';
|
||||
import { I, UtilCommon, UtilObject } from 'Lib';
|
||||
import { extensionStore } from 'Store';
|
||||
import Url from 'json/url.json';
|
||||
|
||||
interface State {
|
||||
error: string;
|
||||
};
|
||||
|
||||
const Success = observer(class Success extends React.Component<I.PageComponent, State> {
|
||||
|
||||
constructor (props: I.PageComponent) {
|
||||
super(props);
|
||||
|
||||
this.onOpen = this.onOpen.bind(this);
|
||||
};
|
||||
|
||||
render () {
|
||||
const object = extensionStore.createdObject;
|
||||
|
||||
if (!object) {
|
||||
return null;
|
||||
};
|
||||
|
||||
const name = object.name || UtilObject.defaultName('Page');
|
||||
|
||||
return (
|
||||
<div className="page pageSuccess">
|
||||
<div className="label bold">{UtilCommon.sprintf('"%s" is saved!', UtilCommon.shorten(name, 64))}</div>
|
||||
<div className="label">{object.description}</div>
|
||||
|
||||
<div className="buttons">
|
||||
<Button color="blank" className="c32" text="Open in app" onClick={this.onOpen} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
onOpen () {
|
||||
window.open(Url.protocol + UtilObject.route(extensionStore.createdObject));
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
export default Success;
|
75
extension/scss/common.scss
Normal file
|
@ -0,0 +1,75 @@
|
|||
html.anytypeWebclipper-iframe,
|
||||
html.anytypeWebclipper-popup {
|
||||
@import "~scss/font.scss";
|
||||
@import "~scss/_vars";
|
||||
|
||||
/* Text */
|
||||
|
||||
--color-text-primary: #252525;
|
||||
--color-text-secondary: #949494;
|
||||
--color-text-tertiary: #bfbfbf;
|
||||
--color-text-inversion: #fff;
|
||||
|
||||
/* Shape */
|
||||
|
||||
--color-shape-primary: #e3e3e3;
|
||||
--color-shape-secondary: #ebebeb;
|
||||
--color-shape-tertiary: #f2f2f2;
|
||||
|
||||
--color-shape-highlight-medium: rgba(79, 79, 79, 0.08);
|
||||
--color-shape-highlight-light: rgba(79, 79, 79, 0.04);
|
||||
|
||||
/* Control */
|
||||
|
||||
--color-control-accent: #252525;
|
||||
--color-control-active: #b6b6b6;
|
||||
--color-control-inactive: #dcdcdc;
|
||||
--color-control-bg: #fff;
|
||||
|
||||
/* Background */
|
||||
|
||||
--color-bg-primary: #fff;
|
||||
--color-bg-loader: rgba(255,255,255,0.7);
|
||||
|
||||
/* System */
|
||||
|
||||
--color-system-accent-100: #ffb522;
|
||||
--color-system-accent-50: #ffd15b;
|
||||
--color-system-accent-25: #ffee94;
|
||||
--color-system-selection: rgba(24, 163, 241, 0.15);
|
||||
--color-system-drop-zone: rgba(255, 187, 44, 0.25);
|
||||
|
||||
/* Color */
|
||||
|
||||
--color-yellow: #ecd91b;
|
||||
--color-orange: #ffb522;
|
||||
--color-red: #f55522;
|
||||
--color-pink: #e51ca0;
|
||||
--color-purple: #ab50cc;
|
||||
--color-blue: #3e58eb;
|
||||
--color-ice: #2aa7ee;
|
||||
--color-teal: #0fc8ba;
|
||||
--color-lime: #5dd400;
|
||||
--color-green: #57c600;
|
||||
|
||||
@import "~scss/common.scss";
|
||||
@import "~scss/form/common.scss";
|
||||
@import "~scss/component/common.scss";
|
||||
@import "~scss/menu/common.scss";
|
||||
@import "~scss/block/common";
|
||||
|
||||
* { box-sizing: border-box; border: 0px; margin: 0px; padding: 0px; }
|
||||
body { font-family: 'Inter'; @include text-common; }
|
||||
|
||||
.loaderWrapper { position: fixed; left: 0px; top: 0px; width: 100%; height: 100%; background: var(--color-bg-loader); z-index: 10; }
|
||||
|
||||
.menus {
|
||||
.menu.vertical {
|
||||
.item { cursor: pointer; }
|
||||
}
|
||||
}
|
||||
|
||||
.button, .select, .cellContent { cursor: pointer; }
|
||||
|
||||
.tagItem { @include text-small; }
|
||||
}
|
22
extension/scss/iframe.scss
Normal file
|
@ -0,0 +1,22 @@
|
|||
#anytypeWebclipper-iframe {
|
||||
@import "~scss/_vars";
|
||||
|
||||
.page.pageCreate { display: flex; flex-direction: column; }
|
||||
.page.pageCreate {
|
||||
.head { display: flex; flex-direction: row; align-items: center; padding: 8px; }
|
||||
.head {
|
||||
.side { display: flex; flex-direction: row; align-items: center; width: 50%; gap: 0px 14px; }
|
||||
.side.right { justify-content: flex-end; }
|
||||
|
||||
.button.simple { @include text-common; font-weight: 500; cursor: pointer; height: 32px; line-height: 32px; }
|
||||
.select { cursor: pointer; height: 32px; line-height: 32px; border: 0px; padding: 8px 20px 8px 8px; }
|
||||
.select {
|
||||
.item {
|
||||
.name { line-height: 16px; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.blocks { padding: 20px 48px 48px 0px; height: 100%; overflow: auto; }
|
||||
}
|
||||
}
|
50
extension/scss/popup.scss
Normal file
|
@ -0,0 +1,50 @@
|
|||
html.anytypeWebclipper-popup { width: 268px; }
|
||||
|
||||
#anytypeWebclipper-popup {
|
||||
@import "~scss/_vars";
|
||||
|
||||
.menus {
|
||||
.menu.vertical { width: calc(100% - 32px); left: 16px; }
|
||||
.menu.vertical {
|
||||
.wrap, .items, .scrollArea, .ReactVirtualized__List { border-radius: inherit; }
|
||||
}
|
||||
}
|
||||
|
||||
.input, .select, .textarea { @include text-small; border: 1px solid var(--color-shape-secondary); width: 100%; border-radius: 1px; }
|
||||
|
||||
.input, .select { height: 32px; padding: 0px 10px; }
|
||||
.select { display: flex; align-items: center; }
|
||||
.textarea { padding: 10px; resize: none; height: 68px; display: block; }
|
||||
.buttons { display: flex; flex-direction: column; justify-content: center; gap: 8px 0px; margin: 16px 0px 0px 0px; }
|
||||
.loaderWrapper { position: fixed; left: 0px; top: 0px; width: 100%; height: 100%; background: var(--color-bg-loader); z-index: 10; }
|
||||
.error { @include text-small; margin: 1em 0px 0px 0px; }
|
||||
|
||||
.isFocused { border-color: var(--color-ice) !important; box-shadow: 0px 0px 0px 1px var(--color-ice); }
|
||||
|
||||
.page.pageIndex, .page.pageChallenge { padding: 50px 16px; text-align: center; }
|
||||
|
||||
.page.pageCreate { padding: 16px; }
|
||||
.page.pageCreate {
|
||||
.row { margin: 0px 0px 10px 0px; }
|
||||
.row:last-child { margin: 0px; }
|
||||
|
||||
.label { @include text-small; color: var(--color-text-secondary); margin: 0px 0px 4px 0px; }
|
||||
|
||||
.box { border: 1px solid var(--color-shape-secondary); border-radius: 1px; min-height: 32px; }
|
||||
.box {
|
||||
.value { padding: 6px 10px 0px 10px; }
|
||||
.value {
|
||||
.itemWrap { margin: 0px 6px 6px 0px; }
|
||||
}
|
||||
|
||||
.entryWrap { position: relative; line-height: 18px; display: inline; vertical-align: top; }
|
||||
.cellContent { height: auto !important; position: relative; box-shadow: 0px 0px; }
|
||||
}
|
||||
}
|
||||
|
||||
.page.pageSuccess { padding: 16px; text-align: center; }
|
||||
.page.pageSuccess {
|
||||
.label { @include text-small; }
|
||||
.label.bold { @include text-common; font-weight: 600; }
|
||||
}
|
||||
}
|
378
go/nativeMessagingHost.go
Normal file
|
@ -0,0 +1,378 @@
|
|||
/*
|
||||
- This is the native messaging host for the AnyType browser extension.
|
||||
- It enables the web extension to find the open ports of the AnyType application and to start it if it is not running.
|
||||
- It is installed by the Electron script found in electron/js/lib/installNativeMessagingHost.js
|
||||
- for more docs, checkout the webclipper repository: https://github.com/anytypeio/webclipper
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
|
||||
// UTILITY FUNCTIONS
|
||||
|
||||
// splits stdout into an array of lines, removing empty lines
|
||||
func splitStdOutLines(stdout string) []string {
|
||||
lines := strings.Split(stdout, "\n")
|
||||
filteredLines := make([]string, 0)
|
||||
for _, line := range lines {
|
||||
if len(line) > 0 {
|
||||
filteredLines = append(filteredLines, line)
|
||||
}
|
||||
}
|
||||
return filteredLines
|
||||
}
|
||||
|
||||
// splits stdout into an array of tokens, replacing tabs with spaces
|
||||
func splitStdOutTokens(line string) []string {
|
||||
return strings.Fields(strings.Replace(line, "\t", " ", -1))
|
||||
}
|
||||
|
||||
// executes a command and returns the stdout as string
|
||||
func execCommand(command string) (string, error) {
|
||||
stdout, err := exec.Command("bash", "-c", command).Output()
|
||||
return string(stdout), err
|
||||
}
|
||||
|
||||
// checks if a string is contained in an array of strings
|
||||
func contains(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CORE LOGIC
|
||||
|
||||
// Windows: returns a list of open ports for all instances of anytypeHelper.exe found using cli utilities tasklist, netstat and findstr
|
||||
func getOpenPortsWindows() (map[string][]string, error) {
|
||||
appName := "anytypeHelper.exe"
|
||||
stdout, err := execCommand(`tasklist | findstr "` + appName + `"`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lines := splitStdOutLines(stdout)
|
||||
pids := map[string]bool{}
|
||||
for _, line := range lines {
|
||||
tokens := splitStdOutTokens(line)
|
||||
pids[tokens[1]] = true
|
||||
}
|
||||
|
||||
if len(pids) == 0 {
|
||||
return nil, errors.New("application not running")
|
||||
}
|
||||
|
||||
result := map[string][]string{}
|
||||
for pid := range pids {
|
||||
stdout, err := execCommand(`netstat -ano | findstr ${pid} | findstr LISTENING`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lines := splitStdOutLines(stdout)
|
||||
ports := map[string]bool{}
|
||||
for _, line := range lines {
|
||||
tokens := splitStdOutTokens(line)
|
||||
port := strings.Split(tokens[1], ":")[1]
|
||||
ports[port] = true
|
||||
}
|
||||
|
||||
portsSlice := []string{}
|
||||
for port := range ports {
|
||||
portsSlice = append(portsSlice, port)
|
||||
}
|
||||
|
||||
result[pid] = portsSlice
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// MacOS and Linux: returns a list of all open ports for all instances of anytype found using cli utilities lsof and grep
|
||||
func getOpenPortsUnix() (map[string][]string, error) {
|
||||
// execute the command
|
||||
appName := "anytype"
|
||||
stdout, err := execCommand(`lsof -i -P -n | grep LISTEN | grep "` + appName + `"`)
|
||||
Trace.Print(`lsof -i -P -n | grep LISTEN | grep "` + appName + `"`)
|
||||
if err != nil {
|
||||
Trace.Print(err)
|
||||
return nil, err
|
||||
}
|
||||
// initialize the result map
|
||||
result := make(map[string][]string)
|
||||
// split the output into lines
|
||||
lines := splitStdOutLines(stdout)
|
||||
for _, line := range lines {
|
||||
|
||||
// normalize whitespace and split into tokens
|
||||
tokens := splitStdOutTokens(line)
|
||||
pid := tokens[1]
|
||||
port := strings.Split(tokens[8], ":")[1]
|
||||
|
||||
// add the port to the result map
|
||||
if _, ok := result[pid]; !ok {
|
||||
result[pid] = []string{}
|
||||
}
|
||||
|
||||
if !contains(result[pid], port) {
|
||||
result[pid] = append(result[pid], port)
|
||||
}
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
return nil, errors.New("application not running")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Windows, MacOS and Linux: returns a list of all open ports for all instances of anytype found using cli utilities
|
||||
func getOpenPorts() (map[string][]string, error) {
|
||||
// Get Platform
|
||||
platform := runtime.GOOS
|
||||
|
||||
Trace.Print("Getting Open Ports on Platform: " + platform)
|
||||
|
||||
// Platform specific functions
|
||||
if platform == "windows" {
|
||||
return getOpenPortsWindows()
|
||||
} else if platform == "darwin" {
|
||||
return getOpenPortsUnix()
|
||||
} else if platform == "linux" {
|
||||
return getOpenPortsUnix()
|
||||
} else {
|
||||
return nil, errors.New("unsupported platform")
|
||||
}
|
||||
}
|
||||
|
||||
// Windows, MacOS and Linux: Starts AnyType as a detached process and returns the PID
|
||||
func startApplication() (int, error) {
|
||||
platform := runtime.GOOS
|
||||
executablePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// /Resources/app.asar.unpacked/dist/executable
|
||||
appPath := filepath.Dir(filepath.Dir(filepath.Dir(filepath.Dir(executablePath))))
|
||||
if platform == "windows" {
|
||||
appPath = filepath.Join(appPath, "Anytype.exe")
|
||||
} else if platform == "darwin" {
|
||||
appPath = filepath.Join(appPath, "MacOS", "Anytype")
|
||||
} else if platform == "linux" {
|
||||
appPath = filepath.Join(appPath, "anytype")
|
||||
} else {
|
||||
return 0, errors.New("unsupported platform")
|
||||
}
|
||||
Trace.Print("Starting Application on Platform: " + platform + " with Path: " + appPath)
|
||||
sub := exec.Command(appPath)
|
||||
err = sub.Start()
|
||||
sub.Process.Release()
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return sub.Process.Pid, nil
|
||||
}
|
||||
|
||||
// MESSAGING LOGIC
|
||||
|
||||
// constants for Logger
|
||||
var (
|
||||
// Trace logs general information messages.
|
||||
Trace *log.Logger
|
||||
// Error logs error messages.
|
||||
Error *log.Logger
|
||||
)
|
||||
|
||||
// nativeEndian used to detect native byte order
|
||||
var nativeEndian binary.ByteOrder
|
||||
|
||||
// bufferSize used to set size of IO buffer - adjust to accommodate message payloads
|
||||
var bufferSize = 8192
|
||||
|
||||
// IncomingMessage represents a message sent to the native host.
|
||||
type IncomingMessage struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// OutgoingMessage respresents a response to an incoming message query.
|
||||
type OutgoingMessage struct {
|
||||
Type string `json:"type"`
|
||||
Response interface{} `json:"response"`
|
||||
Error interface{} `json:"error"`
|
||||
}
|
||||
|
||||
// Init initializes logger and determines native byte order.
|
||||
func Init(traceHandle io.Writer, errorHandle io.Writer) {
|
||||
Trace = log.New(traceHandle, "TRACE: ", log.Ldate|log.Ltime|log.Lshortfile)
|
||||
Error = log.New(errorHandle, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)
|
||||
|
||||
// determine native byte order so that we can read message size correctly
|
||||
var one int16 = 1
|
||||
b := (*byte)(unsafe.Pointer(&one))
|
||||
if *b == 0 {
|
||||
nativeEndian = binary.BigEndian
|
||||
} else {
|
||||
nativeEndian = binary.LittleEndian
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
file, err := os.OpenFile("nmh.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
Init(os.Stdout, os.Stderr)
|
||||
Error.Printf("Unable to create and/or open log file. Will log to Stdout and Stderr. Error: %v", err)
|
||||
} else {
|
||||
Init(file, file)
|
||||
// ensure we close the log file when we're done
|
||||
defer file.Close()
|
||||
}
|
||||
|
||||
Trace.Printf("Chrome native messaging host started. Native byte order: %v.", nativeEndian)
|
||||
read()
|
||||
Trace.Print("Chrome native messaging host exited.")
|
||||
}
|
||||
|
||||
// read Creates a new buffered I/O reader and reads messages from Stdin.
|
||||
func read() {
|
||||
v := bufio.NewReader(os.Stdin)
|
||||
// adjust buffer size to accommodate your json payload size limits; default is 4096
|
||||
s := bufio.NewReaderSize(v, bufferSize)
|
||||
Trace.Printf("IO buffer reader created with buffer size of %v.", s.Size())
|
||||
|
||||
lengthBytes := make([]byte, 4)
|
||||
lengthNum := int(0)
|
||||
|
||||
// we're going to indefinitely read the first 4 bytes in buffer, which gives us the message length.
|
||||
// if stdIn is closed we'll exit the loop and shut down host
|
||||
for b, err := s.Read(lengthBytes); b > 0 && err == nil; b, err = s.Read(lengthBytes) {
|
||||
// convert message length bytes to integer value
|
||||
lengthNum = readMessageLength(lengthBytes)
|
||||
Trace.Printf("Message size in bytes: %v", lengthNum)
|
||||
|
||||
// If message length exceeds size of buffer, the message will be truncated.
|
||||
// This will likely cause an error when we attempt to unmarshal message to JSON.
|
||||
if lengthNum > bufferSize {
|
||||
Error.Printf("Message size of %d exceeds buffer size of %d. Message will be truncated and is unlikely to unmarshal to JSON.", lengthNum, bufferSize)
|
||||
}
|
||||
|
||||
// read the content of the message from buffer
|
||||
content := make([]byte, lengthNum)
|
||||
_, err := s.Read(content)
|
||||
if err != nil && err != io.EOF {
|
||||
Error.Fatal(err)
|
||||
}
|
||||
|
||||
// message has been read, now parse and process
|
||||
parseMessage(content)
|
||||
}
|
||||
|
||||
Trace.Print("Stdin closed.")
|
||||
}
|
||||
|
||||
// readMessageLength reads and returns the message length value in native byte order.
|
||||
func readMessageLength(msg []byte) int {
|
||||
var length uint32
|
||||
buf := bytes.NewBuffer(msg)
|
||||
err := binary.Read(buf, nativeEndian, &length)
|
||||
if err != nil {
|
||||
Error.Printf("Unable to read bytes representing message length: %v", err)
|
||||
}
|
||||
return int(length)
|
||||
}
|
||||
|
||||
// parseMessage parses incoming message
|
||||
func parseMessage(msg []byte) {
|
||||
iMsg := decodeMessage(msg)
|
||||
Trace.Printf("Message received: %s", msg)
|
||||
|
||||
// start building outgoing json message
|
||||
oMsg := OutgoingMessage{
|
||||
Type: iMsg.Type,
|
||||
}
|
||||
|
||||
switch iMsg.Type {
|
||||
case "getPorts":
|
||||
|
||||
// Get open ports
|
||||
openPorts, err := getOpenPorts()
|
||||
if err != nil {
|
||||
oMsg.Error = err.Error()
|
||||
} else {
|
||||
oMsg.Response = openPorts
|
||||
}
|
||||
case "launchApp":
|
||||
// Start application
|
||||
pid, err := startApplication()
|
||||
if err != nil {
|
||||
oMsg.Error = err.Error()
|
||||
} else {
|
||||
oMsg.Response = pid
|
||||
}
|
||||
}
|
||||
|
||||
send(oMsg)
|
||||
}
|
||||
|
||||
// decodeMessage unmarshals incoming json request and returns query value.
|
||||
func decodeMessage(msg []byte) IncomingMessage {
|
||||
var iMsg IncomingMessage
|
||||
err := json.Unmarshal(msg, &iMsg)
|
||||
if err != nil {
|
||||
Error.Printf("Unable to unmarshal json to struct: %v", err)
|
||||
}
|
||||
return iMsg
|
||||
}
|
||||
|
||||
// send sends an OutgoingMessage to os.Stdout.
|
||||
func send(msg OutgoingMessage) {
|
||||
byteMsg := dataToBytes(msg)
|
||||
writeMessageLength(byteMsg)
|
||||
|
||||
var msgBuf bytes.Buffer
|
||||
_, err := msgBuf.Write(byteMsg)
|
||||
if err != nil {
|
||||
Error.Printf("Unable to write message length to message buffer: %v", err)
|
||||
}
|
||||
|
||||
_, err = msgBuf.WriteTo(os.Stdout)
|
||||
if err != nil {
|
||||
Error.Printf("Unable to write message buffer to Stdout: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// dataToBytes marshals OutgoingMessage struct to slice of bytes
|
||||
func dataToBytes(msg OutgoingMessage) []byte {
|
||||
byteMsg, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
Error.Printf("Unable to marshal OutgoingMessage struct to slice of bytes: %v", err)
|
||||
}
|
||||
return byteMsg
|
||||
}
|
||||
|
||||
// writeMessageLength determines length of message and writes it to os.Stdout.
|
||||
func writeMessageLength(msg []byte) {
|
||||
err := binary.Write(os.Stdout, nativeEndian, uint32(len(msg)))
|
||||
if err != nil {
|
||||
Error.Printf("Unable to write message length to Stdout: %v", err)
|
||||
}
|
||||
}
|
14
package-lock.json
generated
|
@ -83,7 +83,7 @@
|
|||
"@typescript-eslint/parser": "^6.18.1",
|
||||
"cross-env": "^7.0.2",
|
||||
"css-loader": "^3.6.0",
|
||||
"electron": "^28.1.3",
|
||||
"electron": "^28.2.0",
|
||||
"electron-builder": "^24.6.3",
|
||||
"eslint": "^8.29.0",
|
||||
"eslint-plugin-react": "^7.31.11",
|
||||
|
@ -4963,9 +4963,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/electron": {
|
||||
"version": "28.1.3",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-28.1.3.tgz",
|
||||
"integrity": "sha512-NSFyTo6SndTPXzU18XRePv4LnjmuM9rF5GMKta1/kPmi02ISoSRonnD7wUlWXD2x53XyJ6d/TbSVesMW6sXkEQ==",
|
||||
"version": "28.2.0",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-28.2.0.tgz",
|
||||
"integrity": "sha512-22SylXQQ9IHtwLw4D+Z4Si7OUpeDtpHfJVTjy3yv53iLg5zJKKPOCWT4ZwgYGHQZ0eldyBrYBHF/P9FPd2CcVQ==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@electron/get": "^2.0.0",
|
||||
|
@ -15614,7 +15614,6 @@
|
|||
},
|
||||
"node_modules/open-color/node_modules/npm/node_modules/lodash._baseindexof": {
|
||||
"version": "3.1.0",
|
||||
"extraneous": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
@ -15629,19 +15628,16 @@
|
|||
},
|
||||
"node_modules/open-color/node_modules/npm/node_modules/lodash._bindcallback": {
|
||||
"version": "3.0.1",
|
||||
"extraneous": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/open-color/node_modules/npm/node_modules/lodash._cacheindexof": {
|
||||
"version": "3.0.2",
|
||||
"extraneous": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/open-color/node_modules/npm/node_modules/lodash._createcache": {
|
||||
"version": "3.1.2",
|
||||
"extraneous": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
@ -15655,7 +15651,6 @@
|
|||
},
|
||||
"node_modules/open-color/node_modules/npm/node_modules/lodash._getnative": {
|
||||
"version": "3.9.1",
|
||||
"extraneous": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
@ -15671,7 +15666,6 @@
|
|||
},
|
||||
"node_modules/open-color/node_modules/npm/node_modules/lodash.restparam": {
|
||||
"version": "3.6.1",
|
||||
"extraneous": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[
|
||||
"electron.js",
|
||||
"electron/js/*",
|
||||
"electron/js/**/*",
|
||||
"electron/json/*",
|
||||
"electron/env.json",
|
||||
"electron/img/*",
|
||||
|
@ -12,12 +12,15 @@
|
|||
"dist/main.js.map",
|
||||
"dist/run.js",
|
||||
"dist/embed/**/*",
|
||||
"dist/challenge/**/*",
|
||||
"dist/lib/**/*",
|
||||
"dist/img/**/*",
|
||||
"dist/css/**/*",
|
||||
"dist/js/**/*",
|
||||
"dist/anytypeHelper.exe",
|
||||
"dist/anytypeHelper",
|
||||
"dist/nativeMessagingHost.exe",
|
||||
"dist/nativeMessagingHost",
|
||||
"dist/*.node",
|
||||
"dist/font/**/*",
|
||||
"dist/workers/**/*",
|
||||
|
|
15
package.json
|
@ -15,7 +15,10 @@
|
|||
"start:dev": "npm-run-all --parallel start:watch start:electron-wait-webpack",
|
||||
"start:dev-win": "npm-run-all --parallel start:watch start:electron-wait-webpack-win",
|
||||
"build": "webpack --mode=production --node-env=production --config webpack.config.js",
|
||||
"build:dev": "webpack --mode=development --node-env=development --config webpack.config.js",
|
||||
"build:deps": "webpack --config webpack.node.config.js --stats detailed | grep 'node_modules' | sed -E 's/.*(node_modules[\\/][^\\\\/[:space:]]{1,})[\\\\/].*/\\1/' | uniq | node save-node-deps.js",
|
||||
"build:nmh": "go build -o dist/nativeMessagingHost ./go/nativeMessagingHost.go",
|
||||
"build:nmh-win": "go build -o dist/nativeMessagingHost.exe ./gonativeMessagingHost.go",
|
||||
"dist:mac": "npm run build:deps && webpack --progress --mode=production --node-env=production && DATE=`date '+%Y-%m-%d_%H_%M'` GIT_COMMIT=`git rev-parse --short HEAD` electron-builder --macos --arm64 --x64",
|
||||
"dist:macarm": "npm run build:deps && webpack --mode=production --node-env=production && DATE=`date '+%Y-%m-%d_%H_%M'` GIT_COMMIT=`git rev-parse --short HEAD` electron-builder --macos --arm64",
|
||||
"dist:macamd": "npm run build:deps && webpack --mode=production --node-env=production && DATE=`date '+%Y-%m-%d_%H_%M'` GIT_COMMIT=`git rev-parse --short HEAD` electron-builder --macos --x64",
|
||||
|
@ -59,7 +62,7 @@
|
|||
"@typescript-eslint/parser": "^6.18.1",
|
||||
"cross-env": "^7.0.2",
|
||||
"css-loader": "^3.6.0",
|
||||
"electron": "^28.1.3",
|
||||
"electron": "^28.2.0",
|
||||
"electron-builder": "^24.6.3",
|
||||
"eslint": "^8.29.0",
|
||||
"eslint-plugin-react": "^7.31.11",
|
||||
|
@ -169,10 +172,13 @@
|
|||
"dist/lib",
|
||||
"dist/anytypeHelper",
|
||||
"dist/anytypeHelper.exe",
|
||||
"dist/nativeMessagingHost.exe",
|
||||
"dist/nativeMessagingHost",
|
||||
"dist/font/**/*",
|
||||
"dist/workers/**/*",
|
||||
"dist/*.node",
|
||||
"dist/embed/**/*",
|
||||
"dist/challenge/**/*",
|
||||
"dist/img/**/*",
|
||||
"dist/css/**/*",
|
||||
"dist/js/**/*",
|
||||
|
@ -192,7 +198,7 @@
|
|||
"extraResources": [],
|
||||
"files": [
|
||||
"electron.js",
|
||||
"electron/js/*",
|
||||
"electron/js/**/*",
|
||||
"electron/json/*",
|
||||
"electron/env.json",
|
||||
"electron/img/*",
|
||||
|
@ -204,12 +210,15 @@
|
|||
"dist/main.js.map",
|
||||
"dist/run.js",
|
||||
"dist/embed/**/*",
|
||||
"dist/challenge/**/*",
|
||||
"dist/lib/**/*",
|
||||
"dist/img/**/*",
|
||||
"dist/css/**/*",
|
||||
"dist/js/**/*",
|
||||
"dist/anytypeHelper.exe",
|
||||
"dist/anytypeHelper",
|
||||
"dist/nativeMessagingHost.exe",
|
||||
"dist/nativeMessagingHost",
|
||||
"dist/*.node",
|
||||
"dist/font/**/*",
|
||||
"dist/workers/**/*",
|
||||
|
@ -572,4 +581,4 @@
|
|||
"pre-commit": "npm run precommit && git add licenses.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 720 B After Width: | Height: | Size: 720 B |
|
@ -1,4 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.46967 4.46967C4.76256 4.17678 5.23744 4.17678 5.53033 4.46967L15.5303 14.4697C15.8232 14.7626 15.8232 15.2374 15.5303 15.5303C15.2374 15.8232 14.7626 15.8232 14.4697 15.5303L4.46967 5.53033C4.17678 5.23744 4.17678 4.76256 4.46967 4.46967Z" fill="#252525"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.5303 4.46967C15.2374 4.17678 14.7626 4.17678 14.4697 4.46967L4.46967 14.4697C4.17678 14.7626 4.17678 15.2374 4.46967 15.5303C4.76256 15.8232 5.23744 15.8232 5.53033 15.5303L15.5303 5.53033C15.8232 5.23744 15.8232 4.76256 15.5303 4.46967Z" fill="#252525"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 720 B |
|
@ -50,7 +50,7 @@
|
|||
"notification": 200
|
||||
},
|
||||
|
||||
"extension": {
|
||||
"fileExtension": {
|
||||
"image": [ "jpg", "jpeg", "png", "gif", "svg", "webp" ],
|
||||
"video": [ "mp4", "m4v", "mov" ],
|
||||
"cover": [ "jpg", "jpeg", "png" ],
|
||||
|
@ -97,7 +97,7 @@
|
|||
|
||||
"sidebarRelationKeys": [
|
||||
"id", "spaceId", "name", "description", "snippet", "layout", "type", "iconEmoji", "iconImage", "iconOption", "isReadonly", "isHidden", "isDeleted", "isArchived", "isFavorite", "done",
|
||||
"relationFormat", "fileExt", "fileMimeType", "links", "restrictions", "source", "identityProfileLink"
|
||||
"relationFormat", "fileExt", "fileMimeType", "links", "restrictions", "source", "identityProfileLink", "lastModifiedDate", "lastOpenedDate"
|
||||
],
|
||||
|
||||
"relationRelationKeys": [
|
||||
|
|
7
src/json/extension.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"clipper": {
|
||||
"id": "jkmhmgghdjjbafmkgjmplhemjjnkligf",
|
||||
"name": "Anytype Webclipper",
|
||||
"prefix": "anytypeWebclipper"
|
||||
}
|
||||
}
|
|
@ -40,6 +40,9 @@
|
|||
"commonToday": "Today",
|
||||
"commonTomorrow": "Tomorrow",
|
||||
"commonYesterday": "Yesterday",
|
||||
"commonLastWeek": "Previous 7 days",
|
||||
"commonLastMonth": "Previous 30 days",
|
||||
"commonOlder": "Older",
|
||||
"commonPhrase": "Recovery Phrase",
|
||||
"commonCopy": "Copy",
|
||||
"commonOpen": "Open",
|
||||
|
@ -115,6 +118,7 @@
|
|||
"commonSetName": "Set of %s",
|
||||
"commonClear": "Clear",
|
||||
"commonNewObject": "New Object",
|
||||
"commonSelectObject": "Select object",
|
||||
"commonCopyLink": "Copy link",
|
||||
"commonSidebar": "Sidebar",
|
||||
"commonLanguage": "Language",
|
||||
|
|
|
@ -14,5 +14,6 @@
|
|||
"privacy": "https://anytype.io/app_privacy/",
|
||||
"contact": "mailto:support@anytype.io?subject=Support%20request%2C%20account%20%25accountId%25&body=%0A%0ATechnical%20information%0A----------------------------------------------%0AOS%20version%3A%20%25os%25%0AApp%20version%3A%20%25version%25%0ABuild%20number%3A%20%25build%25%0ALibrary%20version%3A%20%25middleware%25%0AAccount%20ID%3A%20%25accountId%25%0AAnalytics%20ID%3A%20%25analyticsId%25%0ADevice%20ID%3A%20%25deviceId%25",
|
||||
"extendStorage": "mailto:storage@anytype.io?subject=Get%20more%20storage%2C%20account%20%25accountId%25&body=Hi%2C%20Anytype%20team.%20I%20am%20reaching%20out%20to%20request%20an%20increase%20in%20my%20file%20storage%20capacity%20as%20I%20have%20run%20out%20of%20storage.%20My%20current%20limit%20is%20%25storageLimit%25.%20My%20account%20id%20is%20%25accountId%25.%20Cheers%2C%20%25spaceName%25",
|
||||
"gallery": "https://gallery.any.coop"
|
||||
"gallery": "https://gallery.any.coop",
|
||||
"emojiPrefix": "https://anytype-static.fra1.cdn.digitaloceanspaces.com/emojies/"
|
||||
}
|
||||
|
|
38
src/scss/block/latex.scss
Normal file
|
@ -0,0 +1,38 @@
|
|||
@import "~scss/_vars";
|
||||
|
||||
.blocks {
|
||||
.block.blockLatex { padding: 6px 0px; }
|
||||
.block.blockLatex {
|
||||
.wrap { padding: 2px 0px; }
|
||||
.wrap.isEditing { padding: 8px; box-shadow: 0px 0px 0px 1px $colorShapePrimary; border-radius: 6px; }
|
||||
.wrap.isEditing {
|
||||
.empty { padding-bottom: 18px; }
|
||||
#input { display: block; }
|
||||
.select { display: inline-block; }
|
||||
}
|
||||
|
||||
.selectWrap { text-align: left; }
|
||||
.selectWrap {
|
||||
.select {
|
||||
border: 0px; color: $colorControlActive; @include text-common; border-radius: 0px; padding: 0px 20px 0px 0px;
|
||||
display: none; margin-bottom: 8px;
|
||||
}
|
||||
.select {
|
||||
.name { overflow: visible; }
|
||||
}
|
||||
.select:hover, .select.isFocused { background: none; }
|
||||
}
|
||||
|
||||
#value { font-size: 20px; line-height: 20px; width: 100%; }
|
||||
#value:empty { display: none; }
|
||||
#input {
|
||||
background: $colorShapeTertiary; text-align: left; font-family: 'Inter'; padding: 8px; @include text-common;
|
||||
-webkit-user-modify: read-write-plaintext-only; display: none; border-radius: 4px; margin-top: 8px;
|
||||
}
|
||||
|
||||
.katex-display { margin: 0px; text-align: inherit; }
|
||||
.katex { line-height: 1.5em; text-align: inherit; }
|
||||
.katex > .katex-html { white-space: normal; }
|
||||
.katex .base { margin-top: 2px; margin-bottom: 2px; }
|
||||
}
|
||||
}
|
|
@ -192,4 +192,7 @@ search.active { background: orange !important; }
|
|||
|
||||
.icon.resize { display: none; }
|
||||
.icon.download { display: none; }
|
||||
}
|
||||
}
|
||||
|
||||
@import "scss/debug.scss";
|
||||
@import "scss/font.scss";
|
|
@ -22,8 +22,11 @@
|
|||
.fill { position: absolute; left: 0px; top: 0px; height: 100%; background: var(--color-control-accent); transition: width 0.2s linear; }
|
||||
}
|
||||
|
||||
.icon.close { width: 20px; height: 20px; position: absolute; top: 14px; right: 14px; background-image: url('~img/icon/progress/close0.svg'); }
|
||||
.icon.close:hover { background-image: url('~img/icon/progress/close1.svg'); }
|
||||
.icon.close {
|
||||
width: 24px; height: 24px; position: absolute; top: 14px; right: 14px; background-image: url('~img/icon/progress/close.svg');
|
||||
cursor: default; background-size: 20px; border-radius: 4px;
|
||||
}
|
||||
.icon.close:hover { background-color: var(--color-shape-highlight-medium); }
|
||||
}
|
||||
|
||||
.progress.hide { background: rgba(0,0,0,0); }
|
||||
|
|
|
@ -13,7 +13,10 @@
|
|||
}
|
||||
|
||||
.button.orange { background: var(--color-system-accent-100); color: var(--color-bg-primary); }
|
||||
.button.orange:not(.disabled):hover, .button.orange:not(.disabled).hover { background: var(--color-system-accent-100); }
|
||||
.button.orange:not(.disabled):hover, .button.orange:not(.disabled).hover { background: #f09c0e; }
|
||||
|
||||
.button.pink { background: #ff6a7b; color: var(--color-text-inversion); }
|
||||
.button.pink:not(.disabled):hover { background: #e5374b; }
|
||||
|
||||
.button.black { background: var(--color-control-accent); color: var(--color-bg-primary); }
|
||||
.button.black:not(.disabled):hover, .button.black:not(.disabled).hover { background: #41403d; }
|
||||
|
@ -34,6 +37,7 @@
|
|||
.button.blank:not(.disabled).hover { background: var(--color-shape-highlight-medium); }
|
||||
|
||||
.button.c36 { @include text-common; height: 36px; border-radius: 6px; padding: 0px 12px; }
|
||||
.button.c32{ @include text-small; height: 32px; border-radius: 6px; padding: 0px 10px; }
|
||||
.button.c28 { @include text-common; height: 28px; border-radius: 6px; padding: 0px 10px; }
|
||||
.button.c16 { @include text-9; height: 16px; border-radius: 4px; padding: 0px 4px; }
|
||||
|
||||
|
@ -44,9 +48,5 @@ input.button { line-height: 1; }
|
|||
.arrow { display: inline-block; width: 8px; height: 8px; margin: 0px 0px 0px 4px; }
|
||||
}
|
||||
|
||||
.button.simple {
|
||||
height: auto; padding: 0px; color: var(--color-control-active); font-weight: bold; line-height: 1.43;
|
||||
letter-spacing: 0.1px;
|
||||
}
|
||||
.button.simple:hover,
|
||||
.button.simple.hover { color: var(--color-text-primary); }
|
||||
.button.simple { height: auto; padding: 0px; color: var(--color-control-active); font-weight: bold; line-height: 1.43; letter-spacing: 0.1px; }
|
||||
.button.simple:hover, .button.simple.hover { color: var(--color-text-primary); }
|
|
@ -5,7 +5,7 @@
|
|||
transition: $transitionAllCommon; position: relative; font-family: 'Inter';
|
||||
white-space: nowrap; padding: 3px 20px 3px 6px; line-height: 20px !important;
|
||||
}
|
||||
.select:hover, .select.active { background: var(--color-shape-tertiary); }
|
||||
.select:hover, .select.isFocused { background: var(--color-shape-tertiary); }
|
||||
|
||||
.select {
|
||||
.icon { transition: none; }
|
||||
|
@ -22,6 +22,8 @@
|
|||
.caption { display: none; }
|
||||
}
|
||||
.item::before { display: none; }
|
||||
|
||||
.clickable { display: flex; }
|
||||
}
|
||||
|
||||
.select.big { padding: 9px 26px 9px 12px; border-radius: 10px; }
|
||||
|
|
|
@ -125,7 +125,7 @@
|
|||
|
||||
.iconObject { margin-right: 6px; vertical-align: top; flex-shrink: 0; }
|
||||
|
||||
.clickable { display: flex; flex-grow: 1; width: 100%; }
|
||||
.clickable { display: flex; flex-grow: 1; width: 100%; align-items: center; }
|
||||
|
||||
.select { height: 20px; padding-top: 0px; padding-bottom: 0px; }
|
||||
.select {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
}
|
||||
|
||||
.select { overflow: hidden; border: 0px; padding: 0px; display: block; }
|
||||
.select:hover, .select.active { background: none; }
|
||||
.select:hover, .select.isFocused { background: none; }
|
||||
.select {
|
||||
.icon.relation { display: none; }
|
||||
.icon.arrow { display: none; }
|
||||
|
@ -61,7 +61,7 @@
|
|||
.menu.menuDataviewFilterValues { width: 288px; }
|
||||
.menu.menuDataviewFilterValues {
|
||||
.select { border: 0px; padding: 0px; display: block; width: 100%; }
|
||||
.select:hover, .select.active { background: none; }
|
||||
.select:hover, .select.isFocused { background: none; }
|
||||
.select {
|
||||
.icon.arrow { background-image: url('~img/arrow/filter.svg') !important; }
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
.iconObject { margin-right: 10px; }
|
||||
|
||||
.select { border: 0px; padding: 0px; display: block; }
|
||||
.select:hover, .select.active { background: none; }
|
||||
.select:hover, .select.isFocused { background: none; }
|
||||
.select.grey { color: var(--color-control-active); }
|
||||
.select {
|
||||
.icon.relation { display: none; }
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
.name { @include text-overflow-nw; width: 100%; vertical-align: middle; }
|
||||
|
||||
.select { overflow: hidden; border: 0px; padding: 0px; display: block; }
|
||||
.select:hover, .select.active { background: none; }
|
||||
.select:hover, .select.isFocused { background: none; }
|
||||
.select {
|
||||
.icon.relation { display: none; }
|
||||
.icon.arrow { display: none; }
|
||||
|
|
|
@ -1,19 +1,6 @@
|
|||
@import "~scss/_vars";
|
||||
|
||||
.menus {
|
||||
.menu.menuObjectTypeEdit {
|
||||
.content { padding-bottom: 11px; }
|
||||
.wrap { padding: 0px 16px 8px 16px; }
|
||||
.input { border: 1px solid var(--color-shape-secondary); padding: 0px 8px; }
|
||||
.buttons { padding: 0px 14px; margin-top: 12px; }
|
||||
.button { width: 100%; height: 28px; line-height: 28px; }
|
||||
|
||||
.icon.arrow {
|
||||
width: 20px; height: 20px; position: absolute; right: 14px; top: 50%; margin: -10px 0px 0px 0px;
|
||||
background-image: url('~img/arrow/menu.svg');
|
||||
}
|
||||
}
|
||||
|
||||
.menu.menuTypeSuggest {
|
||||
.filter { padding-top: 12px; }
|
||||
.content { max-height: unset; overflow: hidden; padding: 0px; transition: none; }
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
.title { margin-bottom: 10px; }
|
||||
.content { line-height: 20px; color: var(--color-text-secondary); }
|
||||
.select { border: 0px; padding: 3px 20px 3px 4px; cursor: default; }
|
||||
.select:hover, .select.active { background: var(--color-shape-highlight-light); }
|
||||
.select:hover, .select.isFocused { background: var(--color-shape-highlight-light); }
|
||||
|
||||
.icon { width: 20px; height: 20px; margin-right: 6px; }
|
||||
.name { display: inline-block; vertical-align: middle; }
|
||||
|
|
|
@ -168,7 +168,7 @@
|
|||
.sections { display: flex; flex-direction: column; height: 100%; }
|
||||
.sections {
|
||||
.section { margin-bottom: 40px; display: flex; flex-direction: column; gap: 12px 0px; }
|
||||
.section.top { margin-bottom: 16px; }
|
||||
.section.top { margin-bottom: 16px; justify-content: flex-start; flex-direction: row; }
|
||||
.section.bottom { height: 100%; flex-direction: row; align-items: end; margin-bottom: 0; }
|
||||
|
||||
.section {
|
||||
|
|
|
@ -58,6 +58,12 @@
|
|||
}
|
||||
> .dropTarget.targetTop.isOver::before { top: 0px; }
|
||||
> .dropTarget.targetBot.isOver::before { bottom: 0px; }
|
||||
|
||||
.item.isSection {
|
||||
.inner { display: flex; align-items: center; }
|
||||
.label { @include text-small; color: var(--color-text-secondary); @include text-overflow-nw; }
|
||||
}
|
||||
.item.isSection::before { display: none !important; }
|
||||
}
|
||||
|
||||
.widget.active {
|
||||
|
@ -101,4 +107,4 @@
|
|||
|
||||
@import "./space.scss";
|
||||
@import "./list.scss";
|
||||
@import "./tree.scss";
|
||||
@import "./tree.scss";
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import * as React from 'react';
|
||||
import * as hs from 'history';
|
||||
import * as Sentry from '@sentry/browser';
|
||||
import mermaid from 'mermaid';
|
||||
import $ from 'jquery';
|
||||
import raf from 'raf';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
|
@ -28,8 +27,6 @@ import 'react-pdf/dist/cjs/Page/AnnotationLayer.css';
|
|||
import 'react-pdf/dist/cjs/Page/TextLayer.css';
|
||||
|
||||
import 'scss/common.scss';
|
||||
import 'scss/debug.scss';
|
||||
import 'scss/font.scss';
|
||||
import 'scss/component/common.scss';
|
||||
import 'scss/page/common.scss';
|
||||
import 'scss/block/common.scss';
|
||||
|
@ -64,6 +61,7 @@ declare global {
|
|||
|
||||
isWebVersion: boolean;
|
||||
Config: any;
|
||||
AnytypeGlobalConfig: any;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -88,7 +86,7 @@ const rootStore = {
|
|||
|
||||
window.$ = $;
|
||||
|
||||
if (!window.Electron.isPackaged) {
|
||||
if (!UtilCommon.getElectron().isPackaged) {
|
||||
window.Anytype = {
|
||||
Store: rootStore,
|
||||
Lib: {
|
||||
|
@ -134,8 +132,8 @@ enableLogging({
|
|||
*/
|
||||
|
||||
Sentry.init({
|
||||
release: window.Electron.version.app,
|
||||
environment: window.Electron.isPackaged ? 'production' : 'development',
|
||||
release: UtilCommon.getElectron().version.app,
|
||||
environment: UtilCommon.getElectron().isPackaged ? 'production' : 'development',
|
||||
dsn: Constant.sentry,
|
||||
maxBreadcrumbs: 0,
|
||||
beforeSend: (e: any) => {
|
||||
|
@ -206,7 +204,7 @@ class App extends React.Component<object, State> {
|
|||
<div id="root-loader" className="loaderWrapper">
|
||||
<div className="inner">
|
||||
<div className="logo anim from" />
|
||||
<div className="version anim from">{window.Electron.version.app}</div>
|
||||
<div className="version anim from">{UtilCommon.getElectron().version.app}</div>
|
||||
</div>
|
||||
</div>
|
||||
) : ''}
|
||||
|
@ -238,14 +236,16 @@ class App extends React.Component<object, State> {
|
|||
init () {
|
||||
UtilRouter.init(history);
|
||||
|
||||
dispatcher.init(window.Electron.getGlobal('serverAddress'));
|
||||
dispatcher.init(UtilCommon.getElectron().getGlobal('serverAddress'));
|
||||
dispatcher.listenEvents();
|
||||
|
||||
keyboard.init();
|
||||
|
||||
this.registerIpcEvents();
|
||||
Renderer.send('appOnLoad');
|
||||
|
||||
console.log('[Process] os version:', window.Electron.version.system, 'arch:', window.Electron.arch);
|
||||
console.log('[App] version:', window.Electron.version.app, 'isPackaged', window.Electron.isPackaged);
|
||||
console.log('[Process] os version:', UtilCommon.getElectron().version.system, 'arch:', UtilCommon.getElectron().arch);
|
||||
console.log('[App] version:', UtilCommon.getElectron().version.app, 'isPackaged', UtilCommon.getElectron().isPackaged);
|
||||
};
|
||||
|
||||
initStorage () {
|
||||
|
@ -483,7 +483,7 @@ class App extends React.Component<object, State> {
|
|||
popupStore.open('confirm', {
|
||||
data: {
|
||||
title: translate('popupConfirmUpdateDoneTitle'),
|
||||
text: UtilCommon.sprintf(translate('popupConfirmUpdateDoneText'), window.Electron.version.app),
|
||||
text: UtilCommon.sprintf(translate('popupConfirmUpdateDoneText'), UtilCommon.getElectron().version.app),
|
||||
textConfirm: translate('popupConfirmUpdateDoneOk'),
|
||||
canCancel: false,
|
||||
},
|
||||
|
|
|
@ -217,7 +217,7 @@ const BlockCover = observer(class BlockCover extends React.Component<I.BlockComp
|
|||
onIconUser () {
|
||||
const { rootId } = this.props;
|
||||
|
||||
Action.openFile(Constant.extension.cover, paths => {
|
||||
Action.openFile(Constant.fileExtension.cover, paths => {
|
||||
C.FileUpload(commonStore.space, '', paths[0], I.FileType.Image, (message: any) => {
|
||||
if (!message.error.code) {
|
||||
UtilObject.setIcon(rootId, '', message.hash);
|
||||
|
|
|
@ -981,7 +981,7 @@ const BlockDataview = observer(class BlockDataview extends React.Component<Props
|
|||
analytics.event('InlineSetSetSource', { type: isNew ? 'newObject': 'externalObject' });
|
||||
};
|
||||
|
||||
const menuParam = Object.assign({
|
||||
menuStore.open('searchObject', Object.assign({
|
||||
element: $(element),
|
||||
className: 'single',
|
||||
data: {
|
||||
|
@ -994,9 +994,7 @@ const BlockDataview = observer(class BlockDataview extends React.Component<Props
|
|||
addParam,
|
||||
onSelect,
|
||||
}
|
||||
}, param || {});
|
||||
|
||||
menuStore.open('searchObject', menuParam);
|
||||
}, param || {}));
|
||||
};
|
||||
|
||||
onSourceTypeSelect (obj: any) {
|
||||
|
|
|
@ -263,7 +263,7 @@ const Cell = observer(class Cell extends React.Component<Props> {
|
|||
|
||||
case I.RelationType.File: {
|
||||
param = Object.assign(param, {
|
||||
width: width,
|
||||
width,
|
||||
});
|
||||
param.data = Object.assign(param.data, {
|
||||
value: value || [],
|
||||
|
@ -276,7 +276,7 @@ const Cell = observer(class Cell extends React.Component<Props> {
|
|||
case I.RelationType.Select:
|
||||
case I.RelationType.MultiSelect: {
|
||||
param = Object.assign(param, {
|
||||
width: width,
|
||||
width,
|
||||
commonFilter: true,
|
||||
});
|
||||
param.data = Object.assign(param.data, {
|
||||
|
@ -295,7 +295,7 @@ const Cell = observer(class Cell extends React.Component<Props> {
|
|||
|
||||
case I.RelationType.Object: {
|
||||
param = Object.assign(param, {
|
||||
width: width,
|
||||
width,
|
||||
commonFilter: true,
|
||||
});
|
||||
param.data = Object.assign(param.data, {
|
||||
|
@ -324,7 +324,7 @@ const Cell = observer(class Cell extends React.Component<Props> {
|
|||
element: cell,
|
||||
horizontal: I.MenuDirection.Left,
|
||||
offsetY: -height,
|
||||
width: width,
|
||||
width,
|
||||
height: height,
|
||||
});
|
||||
|
||||
|
|
|
@ -262,8 +262,9 @@ const CellObject = observer(class CellObject extends React.Component<I.Cell, Sta
|
|||
|
||||
value = UtilCommon.arrayUnique(value);
|
||||
|
||||
if (maxCount && value.length > maxCount) {
|
||||
value = value.slice(value.length - maxCount, value.length);
|
||||
const length = value.length;
|
||||
if (maxCount && (length > maxCount)) {
|
||||
value = value.slice(length - maxCount, length);
|
||||
};
|
||||
|
||||
if (onChange) {
|
||||
|
|
|
@ -39,6 +39,7 @@ const CellSelect = observer(class CellSelect extends React.Component<I.Cell, Sta
|
|||
const { relation, getRecord, recordId, elementMapper, arrayLimit } = this.props;
|
||||
const { isEditing } = this.state;
|
||||
const record = getRecord(recordId);
|
||||
const placeholder = this.props.placeholder || translate(`placeholderCell${relation.format}`);
|
||||
const isSelect = relation.format == I.RelationType.Select;
|
||||
const cn = [ 'wrap' ];
|
||||
|
||||
|
@ -49,7 +50,6 @@ const CellSelect = observer(class CellSelect extends React.Component<I.Cell, Sta
|
|||
let value = this.getItems();
|
||||
let content = null;
|
||||
|
||||
const placeholder = this.props.placeholder || translate(`placeholderCell${relation.format}`);
|
||||
const length = value.length;
|
||||
|
||||
if (elementMapper) {
|
||||
|
@ -385,8 +385,9 @@ const CellSelect = observer(class CellSelect extends React.Component<I.Cell, Sta
|
|||
|
||||
value = UtilCommon.arrayUnique(value);
|
||||
|
||||
if (maxCount && value.length > maxCount) {
|
||||
value = value.slice(value.length - maxCount, value.length);
|
||||
const length = value.length;
|
||||
if (maxCount && (length > maxCount)) {
|
||||
value = value.slice(length - maxCount, length);
|
||||
};
|
||||
|
||||
if (onChange) {
|
||||
|
|
|
@ -708,7 +708,7 @@ const BlockEmbed = observer(class BlockEmbed extends React.Component<I.BlockComp
|
|||
if (!iframe.length) {
|
||||
iframe = $('<iframe />', {
|
||||
id: 'receiver',
|
||||
src: this.fixAsarPath(`./embed/iframe.html?theme=${commonStore.getThemeClass()}`),
|
||||
src: UtilCommon.fixAsarPath(`./embed/iframe.html?theme=${commonStore.getThemeClass()}`),
|
||||
frameborder: 0,
|
||||
scrolling: 'no',
|
||||
sandbox: sandbox.join(' '),
|
||||
|
@ -970,18 +970,6 @@ const BlockEmbed = observer(class BlockEmbed extends React.Component<I.BlockComp
|
|||
return Math.min(1, Math.max(0, w / rect.width));
|
||||
};
|
||||
|
||||
fixAsarPath (path: string): string {
|
||||
const origin = location.origin;
|
||||
|
||||
let href = location.href;
|
||||
if (origin == 'file://') {
|
||||
href = href.replace('/app.asar/', '/app.asar.unpacked/');
|
||||
href = href.replace('/index.html', '/');
|
||||
path = href + path.replace(/^\.\//, '');
|
||||
};
|
||||
return path;
|
||||
};
|
||||
|
||||
onResizeInit () {
|
||||
console.log('onResizeInit');
|
||||
};
|
||||
|
|
|
@ -75,7 +75,7 @@ const BlockIconUser = observer(class BlockIconUser extends React.Component<I.Blo
|
|||
onUpload () {
|
||||
const { rootId } = this.props;
|
||||
|
||||
Action.openFile(Constant.extension.cover, paths => {
|
||||
Action.openFile(Constant.fileExtension.cover, paths => {
|
||||
this.setState({ loading: true });
|
||||
|
||||
C.FileUpload(commonStore.space, '', paths[0], I.FileType.Image, (message: any) => {
|
||||
|
|
|
@ -42,7 +42,7 @@ const BlockAudio = observer(class BlockAudio extends React.Component<I.BlockComp
|
|||
block={block}
|
||||
icon="audio"
|
||||
textFile={translate('blockAudioUpload')}
|
||||
accept={Constant.extension.audio}
|
||||
accept={Constant.fileExtension.audio}
|
||||
onChangeUrl={this.onChangeUrl}
|
||||
onChangeFile={this.onChangeFile}
|
||||
readonly={readonly}
|
||||
|
|
|
@ -50,7 +50,7 @@ const BlockImage = observer(class BlockImage extends React.Component<I.BlockComp
|
|||
block={block}
|
||||
icon="image"
|
||||
textFile={translate('blockImageUpload')}
|
||||
accept={Constant.extension.image}
|
||||
accept={Constant.fileExtension.image}
|
||||
onChangeUrl={this.onChangeUrl}
|
||||
onChangeFile={this.onChangeFile}
|
||||
readonly={readonly}
|
||||
|
|
|
@ -79,7 +79,7 @@ const BlockPdf = observer(class BlockPdf extends React.Component<I.BlockComponen
|
|||
block={block}
|
||||
icon="pdf"
|
||||
textFile={translate('blockPdfUpload')}
|
||||
accept={Constant.extension.pdf}
|
||||
accept={Constant.fileExtension.pdf}
|
||||
onChangeUrl={this.onChangeUrl}
|
||||
onChangeFile={this.onChangeFile}
|
||||
readonly={readonly}
|
||||
|
@ -224,7 +224,7 @@ const BlockPdf = observer(class BlockPdf extends React.Component<I.BlockComponen
|
|||
const { content } = block;
|
||||
const { hash } = content;
|
||||
|
||||
C.FileDownload(hash, window.Electron.tmpPath(), (message: any) => {
|
||||
C.FileDownload(hash, UtilCommon.getElectron().tmpPath, (message: any) => {
|
||||
if (message.path) {
|
||||
Renderer.send('pathOpen', message.path);
|
||||
};
|
||||
|
|
|
@ -54,7 +54,7 @@ const BlockVideo = observer(class BlockVideo extends React.Component<I.BlockComp
|
|||
block={block}
|
||||
icon="video"
|
||||
textFile={translate('blockVideoUpload')}
|
||||
accept={Constant.extension.video}
|
||||
accept={Constant.fileExtension.video}
|
||||
onChangeUrl={this.onChangeUrl}
|
||||
onChangeFile={this.onChangeFile}
|
||||
readonly={readonly}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import $ from 'jquery';
|
||||
import { I, UtilCommon, Preview } from 'Lib';
|
||||
import { Icon, Loader } from 'Component';
|
||||
|
||||
|
|
|
@ -166,6 +166,7 @@ class Input extends React.Component<Props, State> {
|
|||
|
||||
this.isFocused = true;
|
||||
keyboard.setFocus(true);
|
||||
this.addClass('isFocused');
|
||||
};
|
||||
|
||||
onBlur (e: any) {
|
||||
|
@ -175,6 +176,7 @@ class Input extends React.Component<Props, State> {
|
|||
|
||||
this.isFocused = false;
|
||||
keyboard.setFocus(false);
|
||||
this.removeClass('isFocused');
|
||||
};
|
||||
|
||||
onPaste (e: any) {
|
||||
|
@ -263,19 +265,15 @@ class Input extends React.Component<Props, State> {
|
|||
};
|
||||
|
||||
addClass (v: string) {
|
||||
if (!this._isMounted) {
|
||||
return;
|
||||
if (this._isMounted) {
|
||||
$(this.node).addClass(v);
|
||||
};
|
||||
|
||||
$(this.node).addClass(v);
|
||||
};
|
||||
|
||||
removeClass (v: string) {
|
||||
if (!this._isMounted) {
|
||||
return;
|
||||
if (this._isMounted) {
|
||||
$(this.node).removeClass(v);
|
||||
};
|
||||
|
||||
$(this.node).removeClass(v);
|
||||
};
|
||||
|
||||
setPlaceholder (v: string) {
|
||||
|
|
|
@ -100,7 +100,7 @@ class Select extends React.Component<Props, State> {
|
|||
{current ? (
|
||||
<React.Fragment>
|
||||
{current.map((item: any, i: number) => (
|
||||
<MenuItemVertical key={i} {...item} iconSize={item.iconSize ? 20 : undefined} />
|
||||
<MenuItemVertical key={i} {...item} />
|
||||
))}
|
||||
<Icon className={acn.join(' ')} />
|
||||
</React.Fragment>
|
||||
|
@ -136,6 +136,7 @@ class Select extends React.Component<Props, State> {
|
|||
for (const option of this.props.options) {
|
||||
options.push(option);
|
||||
};
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
|
@ -189,14 +190,14 @@ class Select extends React.Component<Props, State> {
|
|||
element,
|
||||
noFlipX: true,
|
||||
onOpen: () => {
|
||||
window.setTimeout(() => $(element).addClass('active'));
|
||||
window.setTimeout(() => $(element).addClass('isFocused'));
|
||||
|
||||
if (onOpen) {
|
||||
onOpen();
|
||||
};
|
||||
},
|
||||
onClose: () => {
|
||||
window.setTimeout(() => $(element).removeClass('active'));
|
||||
window.setTimeout(() => $(element).removeClass('isFocused'));
|
||||
|
||||
if (onClose) {
|
||||
onClose();
|
||||
|
|
|
@ -130,6 +130,7 @@ class Textarea extends React.Component<Props, State> {
|
|||
};
|
||||
|
||||
keyboard.setFocus(true);
|
||||
this.addClass('isFocused');
|
||||
};
|
||||
|
||||
onBlur (e: any) {
|
||||
|
@ -138,6 +139,7 @@ class Textarea extends React.Component<Props, State> {
|
|||
};
|
||||
|
||||
keyboard.setFocus(false);
|
||||
this.removeClass('isFocused');
|
||||
};
|
||||
|
||||
onCopy (e: any) {
|
||||
|
@ -181,12 +183,19 @@ class Textarea extends React.Component<Props, State> {
|
|||
};
|
||||
|
||||
setError (v: boolean) {
|
||||
if (!this._isMounted) {
|
||||
return;
|
||||
};
|
||||
v ? this.addClass('withError') : this.removeClass('withError');
|
||||
};
|
||||
|
||||
const node = $(this.node);
|
||||
v ? node.addClass('withError') : node.removeClass('withError');
|
||||
addClass (v: string) {
|
||||
if (this._isMounted) {
|
||||
$(this.node).addClass(v);
|
||||
};
|
||||
};
|
||||
|
||||
removeClass (v: string) {
|
||||
if (this._isMounted) {
|
||||
$(this.node).removeClass(v);
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
|
|
@ -269,7 +269,7 @@ const MenuBlockCover = observer(class MenuBlockCover extends React.Component<I.M
|
|||
const { data } = param;
|
||||
const { onUpload, onUploadStart } = data;
|
||||
|
||||
Action.openFile(Constant.extension.cover, paths => {
|
||||
Action.openFile(Constant.fileExtension.cover, paths => {
|
||||
close();
|
||||
|
||||
if (onUploadStart) {
|
||||
|
|
|
@ -383,14 +383,14 @@ const MenuOptionList = observer(class MenuOptionList extends React.Component<I.M
|
|||
|
||||
resize () {
|
||||
const { getId, position, param } = this.props;
|
||||
const { data, title } = param;
|
||||
const { noFilter } = data;
|
||||
const { data } = param;
|
||||
const { noFilter, maxHeight } = data;
|
||||
const items = this.getItems();
|
||||
const obj = $(`#${getId()} .content`);
|
||||
const offset = 16 + (noFilter ? 0 : 38);
|
||||
const height = Math.max(HEIGHT + offset, Math.min(360, items.length * HEIGHT + offset));
|
||||
const height = Math.max(HEIGHT + offset, Math.min(maxHeight || 360, items.length * HEIGHT + offset));
|
||||
|
||||
obj.css({ height: height });
|
||||
obj.css({ height });
|
||||
position();
|
||||
};
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ class MenuHelp extends React.Component<I.Menu> {
|
|||
};
|
||||
|
||||
getItems () {
|
||||
const btn = <Button className="c16" text={window.Electron.version.app} />;
|
||||
const btn = <Button className="c16" text={UtilCommon.getElectron().version.app} />;
|
||||
|
||||
return [
|
||||
{ id: 'whatsNew', document: 'whatsNew', caption: btn },
|
||||
|
|
|
@ -444,6 +444,8 @@ const Menu = observer(class Menu extends React.Component<I.Menu, State> {
|
|||
position () {
|
||||
const { id, param } = this.props;
|
||||
const { element, recalcRect, type, vertical, horizontal, fixedX, fixedY, isSub, noFlipX, noFlipY, withArrow } = param;
|
||||
const borderTop = Number(window.AnytypeGlobalConfig?.menuBorderTop) || UtilCommon.sizeHeader();
|
||||
const borderBottom = Number(window.AnytypeGlobalConfig?.menuBorderBottom) || 80;
|
||||
|
||||
if (this.ref && this.ref.beforePosition) {
|
||||
this.ref.beforePosition();
|
||||
|
@ -467,7 +469,6 @@ const Menu = observer(class Menu extends React.Component<I.Menu, State> {
|
|||
const isFixed = (menu.css('position') == 'fixed') || (node.css('position') == 'fixed');
|
||||
const offsetX = Number(typeof param.offsetX === 'function' ? param.offsetX() : param.offsetX) || 0;
|
||||
const offsetY = Number(typeof param.offsetY === 'function' ? param.offsetY() : param.offsetY) || 0;
|
||||
const minY = UtilCommon.sizeHeader();
|
||||
|
||||
let ew = 0;
|
||||
let eh = 0;
|
||||
|
@ -510,7 +511,7 @@ const Menu = observer(class Menu extends React.Component<I.Menu, State> {
|
|||
y = oy - height + offsetY;
|
||||
|
||||
// Switch
|
||||
if (!noFlipY && (y <= BORDER)) {
|
||||
if (!noFlipY && (y <= borderTop)) {
|
||||
y = oy + eh - offsetY;
|
||||
};
|
||||
break;
|
||||
|
@ -523,7 +524,7 @@ const Menu = observer(class Menu extends React.Component<I.Menu, State> {
|
|||
y = oy + eh + offsetY;
|
||||
|
||||
// Switch
|
||||
if (!noFlipY && (y >= wh - height - 80)) {
|
||||
if (!noFlipY && (y >= wh - height - borderBottom)) {
|
||||
y = oy - height - offsetY;
|
||||
};
|
||||
break;
|
||||
|
@ -563,8 +564,8 @@ const Menu = observer(class Menu extends React.Component<I.Menu, State> {
|
|||
x = Math.max(BORDER, x);
|
||||
x = Math.min(ww - width - BORDER, x);
|
||||
|
||||
y = Math.max(minY, y);
|
||||
y = Math.min(wh - height - 80, y);
|
||||
y = Math.max(borderTop, y);
|
||||
y = Math.min(wh - height - borderBottom, y);
|
||||
|
||||
if (undefined !== fixedX) x = fixedX;
|
||||
if (undefined !== fixedY) y = fixedY;
|
||||
|
|
|
@ -389,6 +389,7 @@ const MenuSearchObject = observer(class MenuSearchObject extends React.Component
|
|||
const { filter, rootId, type, blockId, blockIds, position, onSelect, noClose } = data;
|
||||
const addParam: any = data.addParam || {};
|
||||
const object = detailStore.get(rootId, blockId);
|
||||
const details = data.details || {};
|
||||
|
||||
if (!noClose) {
|
||||
close();
|
||||
|
@ -453,7 +454,7 @@ const MenuSearchObject = observer(class MenuSearchObject extends React.Component
|
|||
addParam.onClick();
|
||||
close();
|
||||
} else {
|
||||
UtilObject.create('', '', { name: filter, type: commonStore.type }, I.BlockPosition.Bottom, '', {}, [ I.ObjectFlag.SelectType, I.ObjectFlag.SelectTemplate ], (message: any) => {
|
||||
UtilObject.create('', '', { name: filter, type: commonStore.type, ...details }, I.BlockPosition.Bottom, '', {}, [ I.ObjectFlag.SelectType, I.ObjectFlag.SelectTemplate ], (message: any) => {
|
||||
UtilObject.getById(message.targetId, (object: any) => { process(object, true); });
|
||||
close();
|
||||
});
|
||||
|
|
|
@ -395,7 +395,7 @@ const MenuSelect = observer(class MenuSelect extends React.Component<I.Menu> {
|
|||
resize () {
|
||||
const { position, getId, param } = this.props;
|
||||
const { data } = param;
|
||||
const { noScroll, noVirtualisation } = data;
|
||||
const { noScroll, maxHeight, noVirtualisation } = data;
|
||||
const items = this.getItems(true);
|
||||
const obj = $(`#${getId()}`);
|
||||
const content = obj.find('.content');
|
||||
|
@ -416,7 +416,7 @@ const MenuSelect = observer(class MenuSelect extends React.Component<I.Menu> {
|
|||
height = items.reduce((res: number, current: any) => res + this.getRowHeight(current), height);
|
||||
};
|
||||
|
||||
height = Math.min(370, height);
|
||||
height = Math.min(maxHeight || 370, height);
|
||||
height = Math.max(44, height);
|
||||
|
||||
content.css({ height });
|
||||
|
|
|
@ -601,7 +601,7 @@ class MenuSmile extends React.Component<I.Menu, State> {
|
|||
|
||||
close();
|
||||
|
||||
Action.openFile(Constant.extension.cover, paths => {
|
||||
Action.openFile(Constant.fileExtension.cover, paths => {
|
||||
C.FileUpload(commonStore.space, '', paths[0], I.FileType.Image, (message: any) => {
|
||||
if (!message.error.code && onUpload) {
|
||||
onUpload(message.hash);
|
||||
|
|
|
@ -139,7 +139,7 @@ const Controls = observer(class Controls extends React.Component<Props, State> {
|
|||
onIconUser () {
|
||||
const { rootId } = this.props;
|
||||
|
||||
Action.openFile(Constant.extension.cover, paths => {
|
||||
Action.openFile(Constant.fileExtension.cover, paths => {
|
||||
C.FileUpload(commonStore.space, '', paths[0], I.FileType.Image, (message: any) => {
|
||||
if (message.hash) {
|
||||
UtilObject.setIcon(rootId, '', message.hash);
|
||||
|
|
|
@ -21,7 +21,6 @@ const PageHeadEditor = observer(class PageHeadEditor extends React.Component<Pro
|
|||
this.onScaleStart = this.onScaleStart.bind(this);
|
||||
this.onScaleMove = this.onScaleMove.bind(this);
|
||||
this.onScaleEnd = this.onScaleEnd.bind(this);
|
||||
this.onClone = this.onClone.bind(this);
|
||||
};
|
||||
|
||||
render (): any {
|
||||
|
@ -129,19 +128,6 @@ const PageHeadEditor = observer(class PageHeadEditor extends React.Component<Pro
|
|||
value.text(Math.ceil(v * 100) + '%');
|
||||
};
|
||||
|
||||
onClone (e: any) {
|
||||
const { rootId } = this.props;
|
||||
const object = detailStore.get(rootId, rootId);
|
||||
|
||||
C.TemplateClone(rootId, (message: any) => {
|
||||
if (message.id) {
|
||||
UtilObject.openRoute({ id: message.id });
|
||||
};
|
||||
|
||||
analytics.event('CreateTemplate', { objectType: object.targetObjectType });
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
export default PageHeadEditor;
|
|
@ -25,7 +25,7 @@ const PageMainEmpty = observer(class PageMainEmpty extends React.Component<I.Pag
|
|||
<Header component="mainEmpty" text={translate('commonSearch')} layout={I.ObjectLayout.SpaceView} {...this.props} />
|
||||
|
||||
<div className="wrapper">
|
||||
<IconObject object={space} size={112} forceLetter={true} />
|
||||
<IconObject object={space} size={96} forceLetter={true} />
|
||||
<Title text={space.name} />
|
||||
<Label text={translate('pageMainEmptyDescription')} />
|
||||
|
||||
|
|
|
@ -255,7 +255,7 @@ const PageMainMedia = observer(class PageMainMedia extends React.Component<I.Pag
|
|||
const block = blocks.find(it => it.isFile());
|
||||
const { content } = block;
|
||||
|
||||
C.FileDownload(content.hash, window.Electron.tmpPath(), (message: any) => {
|
||||
C.FileDownload(content.hash, UtilCommon.getElectron().tmpPath, (message: any) => {
|
||||
if (message.path) {
|
||||
Renderer.send('pathOpen', message.path);
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import { IconObject, Input, Title, Loader, Icon } from 'Component';
|
||||
import { IconObject, Input, Title, Loader, Icon, Error } from 'Component';
|
||||
import { I, C, translate, UtilCommon, Action, UtilObject, UtilRouter } from 'Lib';
|
||||
import { authStore, detailStore, blockStore, menuStore, commonStore } from 'Store';
|
||||
import { observer } from 'mobx-react';
|
||||
|
@ -47,7 +47,7 @@ const PopupSettingsPageAccount = observer(class PopupSettingsPageAccount extends
|
|||
return (
|
||||
<div className="sections">
|
||||
<div className="section top">
|
||||
{error ? <div className="message">{error}</div> : ''}
|
||||
<Error text={error} />
|
||||
|
||||
<div className="iconWrapper">
|
||||
{loading ? <Loader /> : ''}
|
||||
|
@ -132,7 +132,7 @@ const PopupSettingsPageAccount = observer(class PopupSettingsPageAccount extends
|
|||
};
|
||||
|
||||
onUpload () {
|
||||
Action.openFile(Constant.extension.cover, paths => {
|
||||
Action.openFile(Constant.fileExtension.cover, paths => {
|
||||
this.setState({ loading: true });
|
||||
|
||||
C.FileUpload(commonStore.space, '', paths[0], I.FileType.Image, (message: any) => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as React from 'react';
|
||||
import { Icon, Title, Label } from 'Component';
|
||||
import { I, UtilCommon, translate, Action } from 'Lib';
|
||||
import { I, UtilCommon, translate, Action, UtilMenu } from 'Lib';
|
||||
import { observer } from 'mobx-react';
|
||||
import Constant from 'json/constant.json';
|
||||
import Head from '../head';
|
||||
|
@ -46,7 +46,7 @@ const PopupSettingsPageImportIndex = observer(class PopupSettingsPageImportIndex
|
|||
const common = [ I.ImportType.Html, I.ImportType.Text, I.ImportType.Protobuf, I.ImportType.Markdown ];
|
||||
|
||||
if (common.includes(item.format)) {
|
||||
Action.import(item.format, Constant.extension.import[item.format]);
|
||||
Action.import(item.format, Constant.fileExtension.import[item.format]);
|
||||
close();
|
||||
} else {
|
||||
onPage(UtilCommon.toCamelCase('import-' + item.id));
|
||||
|
@ -54,14 +54,7 @@ const PopupSettingsPageImportIndex = observer(class PopupSettingsPageImportIndex
|
|||
};
|
||||
|
||||
getItems () {
|
||||
return [
|
||||
{ id: 'notion', name: 'Notion', format: I.ImportType.Notion },
|
||||
{ id: 'markdown', name: 'Markdown', format: I.ImportType.Markdown },
|
||||
{ id: 'html', name: 'HTML', format: I.ImportType.Html },
|
||||
{ id: 'text', name: 'TXT', format: I.ImportType.Text },
|
||||
{ id: 'protobuf', name: 'Any-Block', format: I.ImportType.Protobuf },
|
||||
{ id: 'csv', name: 'CSV', format: I.ImportType.Csv },
|
||||
];
|
||||
return UtilMenu.getImportFormats();
|
||||
};
|
||||
|
||||
});
|
||||
|
|
|
@ -203,18 +203,17 @@ const PopupSettingsSpaceIndex = observer(class PopupSettingsSpaceIndex extends R
|
|||
C.WorkspaceCreate({ name, iconOption }, usecase, (message: any) => {
|
||||
this.setState({ isLoading: false });
|
||||
|
||||
if (!message.error.code) {
|
||||
analytics.event('CreateSpace', { usecase, middleTime: message.middleTime });
|
||||
analytics.event('SelectUsecase', { type: usecase });
|
||||
|
||||
if (onCreate) {
|
||||
onCreate(message.objectId);
|
||||
};
|
||||
|
||||
close();
|
||||
} else {
|
||||
if (message.error.code) {
|
||||
this.setState({ error: message.error.description });
|
||||
return;
|
||||
};
|
||||
|
||||
if (onCreate) {
|
||||
onCreate(message.objectId);
|
||||
};
|
||||
|
||||
analytics.event('CreateSpace', { usecase, middleTime: message.middleTime });
|
||||
analytics.event('SelectUsecase', { type: usecase });
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -557,7 +557,7 @@ const PopupSearch = observer(class PopupSearch extends React.Component<I.Popup,
|
|||
|
||||
// Import action
|
||||
if (item.isImport) {
|
||||
Action.import(item.format, Constant.extension.import[item.format]);
|
||||
Action.import(item.format, Constant.fileExtension.import[item.format]);
|
||||
|
||||
// Buttons
|
||||
} else {
|
||||
|
|
|
@ -135,8 +135,12 @@ const PopupSettingsOnboarding = observer(class PopupSettingsOnboarding extends R
|
|||
this.props.close();
|
||||
};
|
||||
|
||||
onPathClick (path: string) {
|
||||
Renderer.send('pathOpen', window.Electron.dirname(path));
|
||||
onPathClick () {
|
||||
const { path } = this.config;
|
||||
|
||||
if (path) {
|
||||
Renderer.send('pathOpen', UtilCommon.getElectron().dirname(path));
|
||||
};
|
||||
};
|
||||
|
||||
onChangeStorage () {
|
||||
|
|
|
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||
import raf from 'raf';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Icon, ObjectName, DropTarget } from 'Component';
|
||||
import { C, I, UtilCommon, UtilObject, UtilData, UtilMenu, translate, Storage, Action, analytics, Dataview } from 'Lib';
|
||||
import { C, I, UtilCommon, UtilObject, UtilData, UtilMenu, translate, Storage, Action, analytics, Dataview, UtilDate } from 'Lib';
|
||||
import { blockStore, detailStore, menuStore, dbStore, commonStore } from 'Store';
|
||||
import Constant from 'json/constant.json';
|
||||
|
||||
|
@ -68,6 +68,7 @@ const WidgetIndex = observer(class WidgetIndex extends React.Component<Props> {
|
|||
getData: this.getData,
|
||||
getLimit: this.getLimit,
|
||||
sortFavorite: this.sortFavorite,
|
||||
addGroupLabels: this.addGroupLabels,
|
||||
};
|
||||
|
||||
if (className) {
|
||||
|
@ -677,6 +678,61 @@ const WidgetIndex = observer(class WidgetIndex extends React.Component<Props> {
|
|||
return isPreview ? 0 : limit;
|
||||
};
|
||||
|
||||
addGroupLabels (records: any[], widgetId: string) {
|
||||
const now = UtilDate.now();
|
||||
const { d, m, y } = UtilDate.getCalendarDateParam(now);
|
||||
const today = now - UtilDate.timestamp(y, m, d);
|
||||
const yesterday = now - UtilDate.timestamp(y, m, d - 1);
|
||||
const lastWeek = now - UtilDate.timestamp(y, m, d - 7);
|
||||
const lastMonth = now - UtilDate.timestamp(y, m - 1, d);
|
||||
|
||||
const groups = {
|
||||
today: [],
|
||||
yesterday: [],
|
||||
lastWeek: [],
|
||||
lastMonth: [],
|
||||
older: []
|
||||
};
|
||||
|
||||
let groupedRecords: I.WidgetTreeDetails[] = [];
|
||||
let relationKey;
|
||||
|
||||
if (widgetId == Constant.widgetId.recentOpen) {
|
||||
relationKey = 'lastOpenedDate';
|
||||
};
|
||||
if (widgetId == Constant.widgetId.recentEdit) {
|
||||
relationKey = 'lastModifiedDate';
|
||||
};
|
||||
|
||||
records.forEach((record) => {
|
||||
const diff = now - record[relationKey];
|
||||
|
||||
if (diff < today) {
|
||||
groups.today.push(record);
|
||||
} else
|
||||
if (diff < yesterday) {
|
||||
groups.yesterday.push(record);
|
||||
} else
|
||||
if (diff < lastWeek) {
|
||||
groups.lastWeek.push(record);
|
||||
} else
|
||||
if (diff < lastMonth) {
|
||||
groups.lastMonth.push(record);
|
||||
} else {
|
||||
groups.older.push(record);
|
||||
};
|
||||
});
|
||||
|
||||
Object.keys(groups).forEach((key) => {
|
||||
if (groups[key].length) {
|
||||
groupedRecords.push({ id: key, type: '', links: [], isSection: true });
|
||||
groupedRecords = groupedRecords.concat(groups[key]);
|
||||
};
|
||||
});
|
||||
|
||||
return groupedRecords;
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
export default WidgetIndex;
|
||||
|
|