diff --git a/src/manifest.json b/src/manifest.json
index 443aedc2e..d1607b598 100644
--- a/src/manifest.json
+++ b/src/manifest.json
@@ -1,7 +1,7 @@
{
"name" : "__MSG_extension_name__",
"default_locale" : "en",
- "version" : "1.1.4.6016",
+ "version" : "1.1.4.6022",
"short_name" : "SimpRead",
"description" : "__MSG_extension_desc__",
"homepage_url" : "http://ksria.com/simpread",
diff --git a/src/module/authorize.jsx b/src/module/authorize.jsx
index b8b6bdc82..15480608a 100644
--- a/src/module/authorize.jsx
+++ b/src/module/authorize.jsx
@@ -287,9 +287,15 @@ export default class Auth extends React.Component {
save( state, value ) {
state == "pocket" && ( storage.secret.pocket.tags = value.trim() );
state == "linnk" && ( storage.secret.linnk.group_name = value.trim() );
- state == "notion" && ( storage.secret.notion.folder_id = value.trim() );
- state == "notion" && ( storage.secret.notion.type = this.state.notion.filter( item => item.value == value.trim() )[0].type );
state == "youdao" && ( storage.secret.youdao.folder_id = value.trim() );
+ state == "notion_save_image" && ( storage.secret.notion.save_image = value );
+ if ( state == 'notion' ) {
+ const obj = this.state.notion.filter( item => item.value == value.trim() )[0];
+ storage.secret.notion.folder_id = value.trim();
+ storage.secret.notion.type = obj.type;
+ obj.schema && ( storage.secret.notion.schema = obj.schema );
+ obj.type == "page" && delete storage.secret.notion.schema;
+ }
storage.Safe( () => this.setState({ secret: storage.secret }), storage.secret );
}
@@ -518,14 +524,20 @@ export default class Auth extends React.Component {
onChange={ (s)=>this.onChange( "notion", s ) } />
{ this.state.secret.notion.access_token &&
-
+
{ this.state.notion ? this.save( "notion", v ) } />
:
}
+
+
this.save( "notion_save_image", s ) } />
+ }
sendResponse({ done: response }))
.catch( error => sendResponse({ fail: error }));
+ } else if( request.value.type == 'download' ) {
+ axios( request.value )
+ .then( response => sendResponse({ done: response }))
+ .catch( error => sendResponse({ fail: error }));
}
}
return true;
});
+
+/**
+ * Listen runtime message, include: `notion`
+ */
+const downLoadCache = new Map();
+
+browser.runtime.onMessage.addListener( async function ( request, sender, sendResponse ) {
+ if ( request.type == msg.MESSAGE_ACTION.notion_dl_img ) {
+ try {
+ const option = request.value,
+ { url, protocol } = option,
+ dlRes = await axios({ method: 'get', url: url.replace( /https?:/, protocol ), responseType: 'blob', });
+ let blob = dlRes.data;
+
+ if ( blob.type === 'image/webp' ) {
+ blob = blob.slice( 0, blob.size, 'image/jpeg' );
+ }
+ downLoadCache.set( url, blob );
+ sendResponse({
+ done: {
+ type: blob.type,
+ size: blob.size,
+ url,
+ },
+ });
+ } catch ( err ) {
+ sendResponse({ fail: err });
+ }
+ } else if ( request.type == msg.MESSAGE_ACTION.notion_up_img ) {
+ try {
+ const option = request.value,
+ { url, upUrl } = option,
+ blob = downLoadCache.get( url );
+
+ await axios.put( upUrl, blob, {
+ headers: {
+ 'Content-Type': blob.type,
+ },
+ });
+
+ downLoadCache.delete( url );
+ sendResponse({ done: true });
+ } catch ( err ) {
+ sendResponse({ fail: err });
+ }
+ }
+ return true;
+});
\ No newline at end of file
diff --git a/src/service/export.js b/src/service/export.js
index 874b2e0af..d49744e2b 100644
--- a/src/service/export.js
+++ b/src/service/export.js
@@ -1248,6 +1248,17 @@ class Notion {
return "https://www.notion.so/";
}
+ hasWriteRule(role){
+ return role === 'read_and_write' || role === 'editor'
+ }
+
+ getBlockName(titleArray){
+ if (!titleArray) return 'Undefined'
+ if (Array.isArray(titleArray))
+ return titleArray.map((t) => t[0]).join('')
+ return 'Undefined'
+ }
+
UUID() {
var __extends=void 0&&(void 0).__extends||function(){var _extendStatics=function extendStatics(d,b){_extendStatics=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(d,b){d.__proto__=b}||function(d,b){for(var p in b)if(b.hasOwnProperty(p))d[p]=b[p]};return _extendStatics(d,b)};return function(d,b){_extendStatics(d,b);function __(){this.constructor=d}d.prototype=b===null?Object.create(b):(__.prototype=b.prototype,new __())}}();var ValueUUID=function(){function ValueUUID(_value){this._value=_value;this._value=_value}ValueUUID.prototype.asHex=function(){return this._value};return ValueUUID}();var V4UUID=function(_super){__extends(V4UUID,_super);function V4UUID(){return _super.call(this,[V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),'-',V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),'-','4',V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),'-',V4UUID._oneOf(V4UUID._timeHighBits),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),'-',V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex()].join(''))||this}V4UUID._oneOf=function(array){return array[Math.floor(array.length*Math.random())]};V4UUID._randomHex=function(){return V4UUID._oneOf(V4UUID._chars)};V4UUID._chars=['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'];V4UUID._timeHighBits=['8','9','a','b'];return V4UUID}(ValueUUID);function generateUuid(){return new V4UUID().asHex()}
return generateUuid();
@@ -1261,20 +1272,92 @@ class Notion {
if ( result && status == "success" ) {
this.access_token = Object.values( result.recordMap.notion_user )[0].value.id;
this.blocks = [];
- Object.values( result.recordMap.space ).forEach( item => {
- item.value.pages.forEach( ( id, idx ) => {
- const block = result.recordMap.block[id];
- idx == 0 && this.blocks.push({ name: item.value.name, value: block.value.id, type: block.value.type });
- if ( block.value.type == "page" ) {
- this.blocks.push({ name: " " + ( block.value.properties ? block.value.properties.title[0][0] : "Undefined" ), value: block.value.id, type: "page" });
- } else if ( block.value.type == "collection_view_page" ) {
- Object.values( result.recordMap.collection ).forEach( collection => {
- collection.value.parent_id == block.value.id &&
- this.blocks.push({ name: " " + collection.value.name[0][0], value: collection.value.id, type: "collection" });
- });
+
+ /**
+ * 读取所有空间,并创建映射。
+ */
+ const spaceMaps = {}
+ Object.values( result.recordMap.space ).forEach(({ value: spaceValue, role }) => {
+ if (!this.hasWriteRule(role)) return;
+ spaceMaps[spaceValue.id] = {
+ name : spaceValue.name,
+ value : spaceValue.id,
+ type : 'space',
+ blocks: [],
+ }
+ });
+
+ /**
+ * 读取所有收藏空间,并创建映射。
+ */
+ const collectionMaps = {};
+ Object.values(
+ result.recordMap.collection
+ ).forEach(({ value: collectionValue, role }) => {
+ if (!this.hasWriteRule(role)) return;
+ collectionMaps[ collectionValue.parent_id ] = collectionValue;
+ collectionMaps[ collectionValue.id ] = collectionValue;
+ })
+
+ /**
+ * 遍历所有当前用户能看到的空间。
+ */
+ const processCollection = ( id, spaceBlocks ) => {
+ const collection = collectionMaps[id];
+ if ( !collection ) return;
+
+ let schemaKey;
+ Object.keys( collection.schema ).some( key => {
+ const schema = collection.schema[key];
+ if ( schema.type === 'url' && schema.name === 'URL' ) {
+ schemaKey = key;
+ return true;
}
- });
+ return false;
+ })
+
+ const block = {
+ name : ' ' + this.getBlockName( collection.name ),
+ value : collection.id,
+ type : 'collection',
+ };
+ schemaKey && ( block.schema = schemaKey );
+ spaceBlocks.push( block );
+ }
+ Object.values( result.recordMap.block ).forEach( ({ role, value: blockValue }) => {
+ if ( !this.hasWriteRule( role )) return;
+ const {
+ type,
+ space_id,
+ parent_id,
+ id,
+ collection_id,
+ } = blockValue;
+
+ const _space = space_id ? spaceMaps[space_id] : spaceMaps[parent_id],
+ _spaceBlocks = _space ? _space.blocks : this.blocks;
+
+ if (type == 'page') {
+ _spaceBlocks.push({
+ name : ' ' + this.getBlockName( blockValue.properties.title ),
+ value: id,
+ type : 'page',
+ });
+ } else if ( type == 'collection_view_page' ) {
+ processCollection( id, _spaceBlocks );
+ } else if ( type == 'collection_view' ) {
+ processCollection( collection_id, _spaceBlocks );
+ }
+ });
+
+ Object.values( spaceMaps ).forEach( space => {
+ const { blocks, ...spaceAttr } = space;
+ if ( blocks && blocks.length > 0 ) {
+ this.blocks.push({ name: spaceAttr.name, type: blocks[0].type, value: blocks[0].value });
+ this.blocks.push( ...blocks );
+ }
});
+
this.type = this.blocks[0].type;
this.folder_id = this.blocks[0].value;
callback( result, undefined );
@@ -1285,7 +1368,108 @@ class Notion {
});
}
- Add( title, content, callback ) {
+ MathImages( content ) {
+ const result = content.match( /!\[.*?\]\(http(.*?)\)/g );
+
+ if( !result ) { return []; }
+
+ const images = result.map( o => {
+ const temp = /!\[.*?\]\((http.*?)\)/.exec( o );
+ if ( temp ) {
+ return temp[1];
+ }
+ return '';
+ }).filter( o => o && !~o.indexOf( 'secure.notion-static.com/' ));
+ return images;
+ }
+
+ DonwloadOriginImage( url ) {
+ return new Promise(( resolve, reject ) => {
+ browser.runtime.sendMessage(
+ msg.Add( msg.MESSAGE_ACTION.notion_dl_img, {
+ url,
+ protocol: window.location.protocol
+ }), res => {
+ if ( res.done ) resolve( res.done );
+ else reject( res.fail );
+ });
+ });
+ }
+
+ UploadOriginImage({ type, size, url }) {
+ return new Promise(( resolve, reject ) => {
+ this.GetFileUrl(
+ this.UUID(),
+ urls => {
+ browser.runtime.sendMessage(
+ msg.Add( msg.MESSAGE_ACTION.notion_up_img, {
+ url: url,
+ upUrl: urls.signedPutUrl,
+ }), res => {
+ if ( res.done ) {
+ resolve({ from: url, to: urls.url });
+ } else resolve();
+ }
+ )
+ },
+ {
+ bucket: 'secure',
+ contentType: type,
+ }
+ )
+ })
+ }
+
+ async UploadImages( content ) {
+
+ let completeCount = 0;
+ const images = [ ...new Set( this.MathImages( content ))],
+ imagesCount = images.length,
+ replacements = [],
+ uploadImage = this.UploadOriginImage.bind( this ),
+ notify = new Notify().Render({ state: "loading", content: `正在上传图片到 Notion ,请稍等` }),
+ updateNotify = () => {
+ notify.update( `正在上传图片到 Notion ,当前进度 ${ completeCount }/${ imagesCount }` );
+ },
+ escapeRegExp = str => {
+ return str.replace( /[.*+\-?^${}()|[\]\\]/g, '\\$&' ); // $& means the whole matched string
+ };
+ replacements.push(
+ await images.reduce(( prevPromise, imageUrl ) => {
+ if ( !imageUrl ) return;
+ return prevPromise.then(( replacement ) => {
+ if ( replacement ) {
+ replacements.push( replacement );
+ }
+ completeCount ++;
+ updateNotify();
+ return this.DonwloadOriginImage( imageUrl ).then(
+ uploadImage,
+ err => {
+ console.error(err)
+ }
+ )
+ });
+ }, Promise.resolve())
+ )
+ notify.complete();
+
+ replacements.forEach(( replacement ) => {
+ if ( replacement ) {
+ content = content.replace(
+ new RegExp(escapeRegExp( replacement.from ), 'g' ),
+ replacement.to
+ )
+ }
+ })
+
+ return content;
+ }
+
+ async Add( title, content, callback ) {
+ if ( this.save_image ) {
+ content = await this.UploadImages( content );
+ }
this.TempFile( this.folder_id, title, ( documentId, error ) => {
console.log( 'TempFile: ', documentId )
if ( error ) {
@@ -1409,14 +1593,13 @@ class Notion {
});
}
- GetFileUrl( name, callback ) {
+ GetFileUrl( name, callback, option = { bucket: 'temporary', contentType: 'text/markdown' } ) {
browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.AXIOS, {
type: "post",
url: this.url + "api/v3/getUploadFileUrl",
data:{
- bucket: 'temporary',
name: name,
- contentType: 'text/markdown',
+ ...option
}
}), result => {
if ( result && result.done ) {
@@ -1457,7 +1640,151 @@ class Notion {
},
}
}
- }), result => callback( result ));
+ }), result => {
+ if ( this.type == 'collection' ) {
+ /* result.done && this.CheckQueueTask(result.done.data.taskId, () => {
+ this.SetProperties(documentId, () => callback(result))
+ }) */
+ const taskId = result.done.data.taskId;
+ if ( result.done ) {
+ this.CheckQueueTask( taskId, () => {
+ if ( this.schema ) {
+ this.SetProperties( documentId, () => callback( result ));
+ } else {
+ this.GetCollectionData( collection => {
+ this.AddCollectionUrlSchema( collection, schemaKey => {
+ this.schema = schemaKey;
+ this.SetProperties( documentId, () => callback( result ));
+ });
+ });
+ }
+ })
+ }
+ } else callback( result );
+ });
+ }
+
+ GetCollectionData( callback ) {
+ browser.runtime.sendMessage(
+ msg.Add( msg.MESSAGE_ACTION.AXIOS, {
+ type: 'post',
+ url : this.url + 'api/v3/loadUserContent',
+ }), result => {
+ if (result.done) {
+ const {
+ recordMap: { collection },
+ } = result.done.data,
+ currentCollection = collection[ this.folder_id ].value;
+ callback( currentCollection );
+ }
+ }
+ )
+ }
+
+ AddCollectionUrlSchema( collection, callback ) {
+ const { schema } = collection,
+ schemaKeys = Object.keys( schema ),
+ genSchemaKey = ( len = 4 ) => {
+ let key = '';
+ for ( ; key.length < len; ) {
+ key += String.fromCharCode( 33 + 94 * Math.random());
+ }
+ return key;
+ };
+ let schemaKey = null;
+
+ schemaKeys.some( key => {
+ const schemaItem = schema[key];
+ if ( schemaItem.type === 'url' && schemaItem.name === 'URL' ) {
+ schemaKey = key;
+ return true;
+ }
+ return false;
+ });
+
+ if ( schemaKey ) {
+ callback( schemaKey );
+ return;
+ }
+
+ const newSchema = { ...schema };
+ let newSchemaKey = '';
+ while ( !newSchemaKey && schemaKeys.indexOf(newSchemaKey) < 0 ) {
+ newSchemaKey = genSchemaKey();
+ }
+
+ newSchema[ newSchemaKey ] = { type: 'url', name: 'URL' };
+
+ browser.runtime.sendMessage(
+ msg.Add( msg.MESSAGE_ACTION.AXIOS, {
+ type: 'post',
+ url : this.url + 'api/v3/submitTransaction',
+ data: {
+ operations: [{
+ args: {
+ schema: newSchema,
+ },
+ command: 'update',
+ id: this.folder_id,
+ path: [],
+ table: 'collection',
+ }],
+ },
+ }), result => {
+ if ( result.done ) {
+ callback( newSchemaKey );
+ }
+ }
+ )
+ }
+
+ CheckQueueTask( taskId, callback ) {
+ if ( taskId ) {
+ browser.runtime.sendMessage(
+ msg.Add(msg.MESSAGE_ACTION.AXIOS, {
+ type: 'post',
+ url : this.url + 'api/v3/getTasks',
+ data: {
+ taskIds: [taskId],
+ },
+ }), result => {
+ if ( result.done ) {
+ const { results } = result.done.data;
+ if ( results[0].state !== 'success' ) {
+ setTimeout( () => {
+ this.CheckQueueTask( taskId, callback );
+ }, 1000 );
+ } else callback();
+ }
+ }
+ )
+ }
+ }
+
+ SetProperties( documentId, callback ) {
+ browser.runtime.sendMessage(
+ msg.Add( msg.MESSAGE_ACTION.AXIOS, {
+ type: 'post',
+ url : this.url + 'api/v3/submitTransaction',
+ data: {
+ operations: [{
+ id: documentId,
+ table: 'block',
+ path: ['properties', this.schema],
+ command: 'set',
+ args: [[ window.location.origin, [[ 'a', window.location.href ]]]],
+ }],
+ },
+ }), result => {
+ result.done && callback( documentId, undefined );
+ }
+ )
+ }
+
+ Save( storage ) {
+ storage.Safe( () => {
+ // TO-DO
+ }, storage.secret );
}
}
diff --git a/src/service/message.js b/src/service/message.js
index 57f81c72c..82668d8d4 100644
--- a/src/service/message.js
+++ b/src/service/message.js
@@ -53,6 +53,9 @@ const action = {
// tips
tips : "tips",
tips_norepeat : "tips_norepeat",
+ // notion.so
+ notion_dl_img : "notion_dl_img",
+ notion_up_img : "notion_up_img",
};
/**
diff --git a/src/service/output.js b/src/service/output.js
index 80d104d20..5bcb5978a 100644
--- a/src/service/output.js
+++ b/src/service/output.js
@@ -316,7 +316,15 @@ function action( type, title, desc, content ) {
corbLoader( "load", () => {
notion.access_token = storage.secret.notion.access_token;
notion.folder_id = storage.secret.notion.folder_id;
- notion.Add( title, result.replace( /.jpeg!720/ig, '.jpeg' ), ( result, error ) => {
+ notion.save_image = storage.secret.notion.save_image;
+ notion.schema = storage.secret.notion.schema;
+ notion.type = storage.secret.notion.type;
+ notion.Add( title, result.replace( /.(png|jpe?g)!\d+/ig, '.$1' ).replace( /, 原文地址 \S+\)/i, '\n' ), ( result, error ) => {
+ // hack code
+ if ( notion.type == "collection" && notion.schema != storage.secret.notion.schema ) {
+ storage.secret.notion.schema = notion.schema;
+ notion.Save( storage );
+ }
exp.svcCbWrapper( result, error, notion.name, type, new Notify() )
});
}, 500 );
diff --git a/src/service/storage.js b/src/service/storage.js
index 133b775e5..fe4113d9a 100644
--- a/src/service/storage.js
+++ b/src/service/storage.js
@@ -253,6 +253,7 @@ let current = {},
"notion" : {
access_token : "",
type : "",
+ save_image : false,
folder_id : "",
},
"youdao" : {
diff --git a/src/service/version.js b/src/service/version.js
index 6034da9d3..e6c3d1af7 100644
--- a/src/service/version.js
+++ b/src/service/version.js
@@ -37,6 +37,7 @@ const version = browser.runtime.getManifest().version.replace( /.\d{2,}/, "" ),
]),
patchs = new Map([
[ "1.1.4.6016", "修复 Notion 相关问题,并支持 Database 导出方案," ],
+ [ "1.1.4.6022", "修复 Notion 授权问题,并支持 图床 导出方案," ],
]),
tips = {
"root" : value => `.version-tips[data-hits='${value}']`,
diff --git a/src/vender/notify/notify.js b/src/vender/notify/notify.js
index 9fe49119a..e7e62e4af 100644
--- a/src/vender/notify/notify.js
+++ b/src/vender/notify/notify.js
@@ -58,7 +58,7 @@
*
*/
var Notify = ( function () {
- var VERSION = "2.0.2.0105",
+ var VERSION = "2.0.2.0621",
name = "notify",
root = "notify-gp",
roottmpl= "<" + root + ">",
@@ -184,6 +184,11 @@ var Notify = ( function () {
this.title ? $title.text( this.title ) : $title.hide();
this.content ? $content.html( this.content ) : $content.hide();
+ this.update = function( content ) {
+ this.content = content;
+ this.content ? $content.html( this.content ) : $content.hide();
+ }
+
if ( this.mode === MODE.modal ) {
$target.addClass( "notify-modal" );
$content.addClass( "notify-modal-content" );