操作指南 使用IPFS和Blockstack構建版本控制系統

2021-01-09 IPFS原力區

Justin HunterIPFS原力區今天

本文由IPFS原力區收集譯製,版權所屬原作者

【前言】

版本控制有很多很好的應用途徑,處理代碼部署、文檔編輯和資料庫快照等一些直接用途。

通常,版本控制系統是資料庫中的另一個部分,但是當你通過不可變數據和DHT(分布式哈希表)技術的角度來思考時,那麼它的作用就會變的更大。所以,今天我們將構建一個具有版本歷史記錄的note-taking應用程式。

這將與其他notes應用程式不同,因為它只有一個notes,用戶可以隨時編輯、刪除信息或添加信息。但我們將包含各個版本信息,以便他們可以獲取他們的歷史版本信息。我們將使用Blockstack和IPFS完成這項工作。

【正文】

Blockstack是一個分散管理的應用程式平臺,允許用戶選擇存儲數據的位置,我們將使用Blockstack提供的存儲中心(免費,不需要配置)。IPFS是一種點對點網絡,允許根據數據的內容而不是其位置來提供數據。這意味著當數據發生變化時,它由不同的標識符(哈希值)表示,舊版本的哈希仍然存在,不會改變。這對於版本控制系統來說是完美的。我們將通過創建一個新的React項目並只安裝一個依賴項SimpleID來構建所有這些。

SimpleID為分布式Web提供開發工具。簡而言之,SimpleID允許開發人員為他們的應用程式添加分散的身份驗證和存儲,而無需讓用戶經歷生成種子短語和管理這些12個單詞備份的繁瑣過程。

用戶可以使用傳統的用戶名/密碼身份驗證流程,同時仍然擁有自己的身份並訪問Web 3.0技術。

首先,訪問SimpleID並註冊一個免費的開發人員帳戶。驗證帳戶後,你將能夠創建項目並選擇要包含在項目中的Web 3.0模塊。讓我們快速瀏覽一下:

註冊開發者帳戶郵箱驗證驗證帳戶後,進入「帳戶」頁面,你可以在其中創建新項目為新項目提供一個名稱和URL,你最終可以在其中託管它(如果是基於https,這可能是一個假網址)保存,然後單擊「查看項目」複製你的API密鑰和開發人員ID轉到「模塊」頁,為你的驗證模塊選擇Blockstack,為你的存儲模塊選擇Blockstack和Pinata單擊「保存」

現在,準備開始了

關於Pinata的簡要說明:它們提供IPFS固定服務,因此SimpleID在後臺使用它們將內容添加到IPFS網絡並固定所述內容,並確保它始終可用。

讓我們建立一個項目;我的說明將從MacOS的角度出發,但在不同系統上的用戶應該能夠使用類似的命令開始。

首先,打開終端並創建新的React項目

npx create-react-app ipfs-blockstack-versioning

完成後,切換到目錄,然後安裝SimpleID依賴項:

cd ipfs-blockstack-versioning

npm i simpleid-js-sdk

在你選擇的文本編輯器中打開項目

我們不打算花時間處理複雜的文件夾結構。這是一個非常基本的應用程式,旨在展示Blockstack和IPFS的強大功能。

考慮到這一點,找到src文件夾並打開App.js. 在該文件的頂部,在import css語句下面添加以下內容:

import { createUserAccount, login, pinContent, fetchPinnedContent } from 'simpleid-js-sdk';const config = {

apiKey: ${yourApiKey}, //found in your SimpleID account page

devId: ${yourDevId}, //found in your SimpleID account page

authProviders: ['blockstack'], //array of auth providers that matches your modules selected

storageProviders: ['blockstack', 'pinata'], //array of storage providers that match the modules you selected

appOrigin: "https://yourapp.com", //This should match the url you provided in your dev account sign up

scopes: ['publish_data', 'store_write', 'email'] //array of permission you are requesting from the user

}

現在導入的SimpleID包和這個配置對象(來自SimpleID文檔),然後就可以開始了。

讓我們來研究一下用戶界面

正如我所提到的,這將是一個非常簡單的應用程式,所以讓我們放入編輯器來處理我們的文檔。

我們將使用index.html文件中的腳本標記來完成此操作,而不是通過NPM安裝依賴項。你可以使用任何WYSIWYG庫,但我將使用的是Medium Editor。你可以在這裡找到它。

index.html文件位於公用文件夾中。找到它並在標題標記上方添加:

<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/medium-editor@latest/dist/css/medium-editor.min.css" type="text/css" media="screen" charset="utf-8">

<script src="//cdn.jsdelivr.net/npm/medium-editor@latest/dist/js/medium-editor.min.js"></script>

<title>NoteStream</title>

你會注意到,我在這裡設置了應用程式的標題,因為我們已經在編輯文件了。可隨意使用相同名稱或創建自己的名稱。

現在我們已經添加了樣式表和所需要的腳本,讓我們轉到位於src文件夾中的App.js文件。我們將清除此文件中的所有內容,並從頭開始。因此,更新app.js文件如下:

import React from 'react';

import './App.css';

import { createUserAccount, login, pinContent, fetchPinnedContent } from 'simpleid-js-sdk';const config = {

apiKey: ${yourApiKey}, //found in your SimpleID account page

devId: ${yourDevId}, //found in your SimpleID account page

authProviders: ['blockstack'], //array of auth providers that matches your modules selected

storageProviders: ['blockstack', 'pinata'], //array of storage providers that match the modules you selected

appOrigin: "https://yourapp.com", //This should match the url you provided in your dev account sign up

scopes: ['publish_data', 'store_write', 'email'] //array of permission you are requesting from the user

}class App extends React.Component {

constructor(props) {

super(props);

this.state = {

userSession,

content: "",

versions: [],

selectedVersionContent: "",

pageRoute: "signup",

versionPane: false,

versionModal: false

}

}

render() {

return (

<div className="App"> </div>

);

}

}export default App;

我已經將函數組件轉換為類組件,但是你可以將其作為函數組件執行,只需對處理狀態的方式進行一些小的更改。

你可以看到我有四個狀態變量,我希望使用:

userSession(將從我們的Blockstack身份驗證中填充)內容(實際的流媒體注釋)版本(這將是我們的歷史信息)selectedVersionContent(用於確定是否顯示版本窗格)pageRoute(用於處理屏幕上顯示的內容)versionPane(確定是否顯示版本窗格)versionModal(用於確定是否打開版本模式)

我們應該做的第一件事是獲得註冊並登錄屏幕渲染。在類名為「app」的<div>中,添加一些具有如下表單輸入的條件邏輯:

render() {

const { pageRoute, userSession } = this.state;

return (

<div className="App">

{

pageRoute === "signup" && !userSession.isUserSignedIn() ?

<div>

Sign Up

</div> :

pageRoute === "signin" && !userSession.isUserSignedIn() ?

<div>

Sign In

</div> :

<div>

App Content

</div>

}

</div>

);

}

很顯然我們會用實際內容填充它,這應該有助於說明正在發生的事情。如果pageroute狀態為「signup」,並且用戶未登錄,則應顯示signup表單。如果pageroute狀態為「sign in」,並且用戶未登錄,則應顯示登錄表單。否則,我們應該顯示應用程式。

現在,讓我們把它建立起來

讓我們從處理Blockstack userSession狀態開始。這非常簡單。在我們的App.js文件的頂部,只需在import語句下面添加:

import { UserSession } from 'blockstack';

import { AppConfig } from 'blockstack'const appConfig = new AppConfig(['store_write', 'publish_data', 'email']);

const userSession = new UserSession({ appConfig });

你應該將它添加到actions.js文件的頂部以及現有的import語句之下。Blockstack隨SimpleID一起安裝,因此你不需要再添加依賴項。好的,現在我們將必要的登錄和註冊表單添加到app.js文件中:

class App extends React.Component {

constructor(props) {

super(props);

this.state = {

userSession,

content: "",

versions: [],

selectedVersionContent: "",

pageRoute: "signup",

versionPane: false,

versionModal: false,

username: "",

password: "",

email: "",

loading: false,

error: "

}

} handleUsername = (e) => {

this.setState({ username: e.target.value });

} handlePassword = (e) => {

this.setState({ password: e.target.value });

} handleEmail = (e) => {

this.setState({ email: e.target.value });

} handleSignIn = (e) => {

e.preventDefault();

} handleSignUp = (e) => {

e.preventDefault();

}render() {

const { pageRoute, userSession, username, password, email, error } = this.state;

return (

<div className="App">

{

pageRoute === "signup" && !userSession.isUserSignedIn() ?

<div>

<form onClick={this.handleSignIn} className="auth-form">

<input placeholder="username" id="username-sign-up" type="text" value={username} onChange={this.handleUsername} />

<input placeholder="password" id="password-sign-up" type="password" value={password} onChange={this.handlePassword} />

<input placeholder="email" id="password-sign-up" type="email" value={email} onChange={this.handleEmail} />

<button type="submit">Sign In</button>

</form>

<p>Already have an account? <button onClick={() => this.setState({ pageRoute: "signin" })} className="button-link">Sign In.</button></p>

<p>{error}</p>

</div> :

pageRoute === "signin" && !userSession.isUserSignedIn() ?

<div>

<form onSubmit={this.handleSignUp} className="auth-form">

<input placeholder="username" id="username-sign-in" type="text" value={username} onChange={this.handleUsername} />

<input placeholder="password" id="password-sign-in" type="password" value={password} onChange={this.handlePassword} />

<button type="submit">Sign In</button>

</form>

<p>Need to sign up? <button onClick={() => this.setState({ pageRoute: "signup" })} className="button-link">Register.</button></p>

<p>{error}</p>

</div> :

<div>

App Content

</div>

}

</div>

);

}

}

export default App;

我們在這裡添加了很多內容,但理解起來非常簡單。

我們添加了處理註冊和登錄流程的功能。我們還添加了一個表單來處理每個輸入。我們添加了一個狀態切換程序,以便登錄表單中的用戶可以切換到註冊表單,反之亦然。我們還在註冊表單和登錄表單中準備了一個段落部分,用於處理註冊或登錄過程中可能發生的任何錯誤。

有了所這些,我們可以啟動我們的應用程式,看看它的運行情況。從終端運行npm開始。

如果確實如此,你會看到一個簡陋的註冊表。你可以切換到登錄表單,也可以切換回來。在本教程中,我們不會涉及太多CSS,但我們已經開始正常運行應用程式。

你可能已經注意到,我添加了一個名為loading的狀態變量。我們將在一秒鐘內使用它,因為我們實際註冊了一個用戶並登錄。我們將從註冊過程開始。同樣,我們將使用SimpleID文檔。

找到handleSignUp函數並填寫如下:

handleSignUp = async (e) => {

e.preventDefault();

this.setState({ loading: true, error: "" });

const { username, password, email } = this.state;

const credObj = {

id: username,

password: password,

hubUrl: 'https://hub.blockstack.org', //This is the default Blockstack storage hub

email: email

} try {

const account = await createUserAccount(credObj, config);

localStorage.setItem('blockstack-session', JSON.stringify(account.body.store.sessionData));

window.location.reload();

} catch(err) {

console.log(err);

this.setState({ loading: false, error: "Trouble signing up..."})

}

}

我們將函數設置為異步的,因為我們需要等待createUserAccount promise才能解決,然後才能執行其他操作。除此之外,我們只是按照文檔添加了try / catch。

如果出現錯誤,將更新錯誤狀態,並將加載狀態設置為false。用戶應該在屏幕上看到錯誤消息。如果沒有錯誤,則更新localStorage項目Blockstack,並刷新窗口。

在測試註冊流程之前,我們應該做的最後一件事是添加加載指示器。這不會有什麼特別的,但註冊時,指標將取代屏幕上的其他所有內容。讓我們更新我們的應用程式代碼JSX,如下所示:

<div className="App">

{

loading ?

<div>

<h1>Loading...</h1>

</div> :

<div> {

pageRoute === "signup" && !userSession.isUserSignedIn() ?

<div>

<div onSubmit={this.handleSignIn} className="auth-form">

<input placeholder="username" id="username-sign-up" type="text" value={username} onChange={this.handleUsername} />

<input placeholder="password" id="password-sign-up" type="password" value={password} onChange={this.handlePassword} />

<input placeholder="email" id="password-sign-up" type="email" value={email} onChange={this.handleEmail} />

<button type="submit">Sign In</button>

</form>

<p>Already have an account? <button onClick={() => this.setState({ pageRoute: "signin" })} className="button-link">Sign In.</button></p>

<p>{error}</p>

</div> :

pageRoute === "signin" && !userSession.isUserSignedIn() ?

<div>

<form onSubmit={this.handleSignUp} className="auth-form">

<input placeholder="username" id="username-sign-in" type="text" value={username} onChange={this.handleUsername} />

<input placeholder="password" id="password-sign-in" type="password" value={password} onChange={this.handlePassword} />

<button type="submit">Sign In</button>

</form>

<p>Need to sign up? <button onClick={() => this.setState({ pageRoute: "signup" })} className="button-link">Register.</button></p>

<p>{error}</p>

</div> :

<div>

App Content

</div>

}

</div>

}

</div>

現在讓我們來測試一下

請輸入用戶名、密碼和電子郵件,然後單擊「註冊」。假設成功了,你應該看到在加載屏幕,幾秒鐘後,用戶登錄並出現「app content」字樣。

但現在,我們尚未處理登錄,用戶無法註銷。讓我們首先處理註銷,因為它非常簡單。在應用程式的「App Content」部分,添加一個調用handleSignOut函數的按鈕:

<button onClick={this.handleSignOut}>Sign Out</button>

然後確保將該函數與其他函數相加:

handleSignOut = () => {

localStorage.removeItem('blockstack-session');

window.location.reload();

}

嘗試一下,用戶應該可以註銷了。現在我們可以登錄了。我希望你能記住用戶名和密碼。讓我們連接handleSignIn函數:

handleSignIn = async (e) => {

e.preventDefault();

this.setState({ loading: true, error: "" });

const { username, password } = this.state;

const credObj = {

id: username,

password,

hubUrl: 'https://hub.blockstack.org' //This is the default Blockstack storage hub

}

const params = {

credObj,

appObj: config,

userPayload: {} //this can be left as an empty object

}

try {

const signIn = await login(params);

if(signIn.message === "user session created") {

localStorage.setItem('blockstack-session', JSON.stringify(signIn.body.store.sessionData));

window.location.reload();

} else {

this.setState({ loading: false, error: signIn.message })

}

} catch(err) {

console.log(err);

this.setState({ error: "Trouble signing in..."})

}

}

我們再次使用SimpleID文檔登錄,大部分代碼都是從註冊函數中重複使用的。我們不需要登錄電子郵件,我們必須創建一個params對象,除此之外,它基本上是相同的。有了這個,讓我們試一試。

你應該已經看到了加載指示符,然後你的用戶處於登錄狀態。當然,當用戶登錄時,我們現在只有一個註銷按鈕。讓我們通過在我們的媒體樣式編輯器中刪除來更改它。

在App.js中的構造函數下方以及其他函數之上,讓我們添加一個componentDidMount方法:

componentDidMount() {

var editor = new window.MediumEditor('.editable');

}

這是使用window來獲取我們添加到index.html文件中的mediumeditor腳本。為了讓我們看到任何東西,我們需要編輯JSX的App Contents部分。因此,在你放置「註銷」按鈕的區域中,讓我們在下面添加一些內容來處理編輯器:

<div className="editor">

<h1>NoteStream</h1>

<p>Start where you left off or shove your thoughts in the middle somewhere. It's up to you!</p>

<div className="editable"></div>

</div>

如果沒有任何CSS樣式,這太難看了。所以,讓我們來修一下。在相同的文件夾App.css文件中,添加以下內容:

.editor {

max-width: 85%;

margin: auto;

margin-top: 100px;

}.editable {

max-width: 85%;

margin: auto;

border: 1px solid #282828;

border-radius: 3px;

min-height: 500px;

padding: 15px;

text-align: left;

}

我們可以稍後更改,但至少可以使應用程式美觀。你應該看到這樣的東西:

不是最漂亮的,但現在可以了。我們需要能夠處理編輯器的更改,所以在開始保存數據之前,我們先從這裡開始。在我們的componentDidMount生命周期事件中,讓我們稍微改變一下:

componentDidMount() {

var editor = new window.MediumEditor('.editable');

//We'll load our content here soon

editor.subscribe('editableInput', (event, editable) => {

this.setState({ content: editor.getContent(0) });

});

}

如果你還記得,我們創建了一個名為content的狀態變量來保存我們的筆記內容。我們在編輯器中的每個更改上設置該狀態。

這意味著當我們準備保存筆記時,我們可以從內容狀態中獲取數據。讓我們通過做兩件事來看看它的外觀。我們將添加一個save按鈕和一個saveContent函數。

在「註銷」按鈕所在的位置下面添加一個「保存」按鈕:

<button onClick={this.handleSignOut}>Sign Out</button>

<button onClick={this.saveContent}>Save</button>

然後,使用所有其他函數創建savecontent函數:

saveContent = () => {

const { content, userSession } = this.state;

console.log(content)

}

我們將在一分鐘內使用userSession狀態,所以我把它扔在那裡。但有了這個,你應該能夠打開開發者控制臺,輸入編輯器,然後點擊「save」。你會看到html內容。

這意味著你已準備好保存內容並加載該內容。不過,讓我們先把這件事做完。我們需要將內容保存到Blockstack的存儲系統和IPFS。Blockstack的存儲系統每次都是覆蓋函數,但對於IPFS,我們將把新版本存儲到網絡中。我們還需要能夠獲取IPFS哈希值,因此我們也應該將其存儲到Blockstack中。

聽起來我覺得我們在Blockstack上有兩個文件存儲:內容和版本(哈希)。但我們必須首先保存到IPFS,以便我們得到哈希結果。讓我們開始在saveContent函數中編寫它。

saveContent = async () => {

const { content, userSession } = this.state;

//First we save to IPFS

const contentToPin = {

pinnedContent: JSON.stringify(content)

}const params = {

devId: config.devId, //your dev ID found in your SimpleID account page

username: userSession.loadUserData().username, //you logged in user's username

id: Date.now(), //an identifier you can use to reference your content later

content: contentToPin, //the content we discussed previously

apiKey: config.apiKey //the api key found in your SimpleID account page

} const pinnedContent = await pinContent(params);

console.log(pinnedContent);

}

我們在函數中添加了async關鍵字,並使用了simpleid文檔提供的將內容發布到ipfs所需的參數。在某些情況下,開發人員需要向Pinata查詢他們之前發布到IPFS的內容。這就是id欄位的全部內容。在本例中,我們將使用blockstack來管理所有的散列,因此我們並不真正關心這個標識符是什麼,除了它是唯一的(因此,date.now())。

讓我們在控制臺打開的情況下測試一下,然後再繼續。向編輯器中添加一些內容,然後單擊「保存」。如果一切順利,你應該在控制臺中看到類似的內容:

{ message: "content successfully pinned", body: "QmbRshi9gjQ2v5tK4B8czPqm3jEQ3zGzsuQJuQLyti4oNc" }

對象中的正文密鑰是IPFS哈希。我們想要使用它並將其存儲為Blockstack的版本。接下來我們來解決這個問題。

saveContent = async () => {

const { content, userSession } = this.state;

//First we save to IPFS

const contentToPin = {

pinnedContent: JSON.stringify(content)

}const params = {

devId: config.devId, //your dev ID found in your SimpleID account page

username: userSession.loadUserData().username, //you logged in user's username

id: Date.now(), //an identifier you can use to reference your content later

content: contentToPin, //the content we discussed previously

apiKey: config.apiKey //the api key found in your SimpleID account page

} if(pinnedContent.message === "content successfully pinned") {

const newVersion = {

timestamp: Date.now(),

hash: pinnedContent.body

}

versions.push(newVersion);

this.setState({ versions });

const savedVersion = await userSession.putFile("version_history.json", JSON.stringify(versions), {encrypt: true});

console.log(savedVersion);

} else {

console.log("Error saving content");

}

}

在嘗試將哈希保存到BuffStash之前,我添加了一個檢查,以確保對IPFS的內容固定成功。我們需要知道版本的時間,所以我們正在構建一個帶有時間戳和哈希本身的newVersion對象,然後我們將它推入版本數組中。然後我們將其保存到Blockstack,在那裡發生了一些很酷的事情。

你可以在putFile調用中看到一個表示加密的對象,我們能夠輕鬆加密數據;這是我用來測試本教程這一部分的文件:

https://gaia.blockstack.org/hub/13ygSWdUeU4gPHbqUzEBvmq1LP7TKNnbtx/version_history.json

這只是加密我們的版本歷史記錄,這很重要。在我們解決保存內容的最後部分之前,在saveContent函數中,關於contentToPin變量,添加以下內容:

const encryptedContent = userSession.encryptContent(JSON.stringify(content), {publicKey: getPublicKeyFromPrivate(userSession.loadUserData().appPrivateKey)});

我們還需要從public函數導入getprivatekeyfrom。因此,在App.js文件的頂部與其他import語句一起添加:

import { getPublicKeyFromPrivate } from 'blockstack/lib/keys';

並將contentToPin變量更新為如下所示:

const contentToPin = {

pinnedContent: JSON.stringify(encryptedContent)

}

我們在設置並保存版本歷史記錄之後再開始。因此,在savedVersions行之後,添加以下內容:

const savedVersion = await userSession.putFile("version_history.json", JSON.stringify(versions), {encrypt: true});const savedContent = await userSession.putFile('note.json', JSON.stringify(encryptedContent), {encrypt: false});console.log(savedContent);

下面是我在控制臺日誌中得到的信息:

https://gaia.blockstack.org/hub/13ygSWdUeU4gPHbqUzEBvmq1LP7TKNnbtx/note.json

回顧一下,我們對內容進行加密,將其存儲在ipfs上,使用返回的IPFS哈希在versions數組中創建新條目,將其保存到Blockstack,然後將當前版本的note內容保存到Blockstack。

我們也需要能夠獲取內容;最初,我們需要在應用程式加載時獲取兩個文件:當前內容(來自note.json)和版本文件(來自version_history.json)。我們應該在應用加載後立即執行此操作,因此需要將其添加到componentDidMount生命周期事件中。

像這樣更新整個事件:

async componentDidMount() {

const { userSession } = this.state;

const content = await userSession.getFile('note.json', {decrypt: false});

const decryptedContent = userSession.decryptContent(JSON.parse(content), {privateKey: userSession.loadUserData().appPrivateKey});

this.setState({ content: JSON.parse(decryptedContent )}); var editor = new window.MediumEditor('.editable');

editor.subscribe('editableInput', (event, editable) => {

this.setState({ content: editor.getContent(0) });

}); editor.setContent(JSON.parse(decryptedContent), 0);

}

保存並返回到你的應用程式。當它重新加載時,你保存的內容現在將顯示在編輯器中。到了這步。我們還有幾件事要做。我們需要加載版本歷史記錄,接下來讓我們這樣做。

在decryptContent變量的正下方,添加以下內容:

const versions = await userSession.getFile('version_history.json', {decrypt: true});this.setState({ content: JSON.parse(decryptedContent), versions: JSON.parse(versions) });

現在,我們可以開始享受歷史版本功能了讓我們先確定我們的版本歷史。在JSX的App Contents部分中,在編輯器下方添加以下內容:

<div className={versionPane ? "versionPaneOpen" : "versionPaneClosed"}>

<ul>

{

versions.map(v => {

return(

<li key={v.timestamp}><a href="#" onClick={() => this.handleVersionModal(v.hash)}>{v.timestamp}</a></li>

)

})

}

</ul>

</div>

我們正在創建一個部分來保存版本歷史記錄。你會注意到className是以狀態變量versionPane為條件的。這是因為我們希望能夠更改該變量並打開版本歷史記錄,而不是一直打開它。讓我們在退出時添加一個按鈕up並保存一個名為Version History的按鈕。

<button onClick={() => this.setState({ versionPane: !versionPane })}>Version History</button>

讓我們再次更新CSS來處理窗格的顯示:

.versionPaneOpen {

position: fixed;

top: 0;

right: 0;

width: 250px;

z-index: 999;

border-left: 2px solid #282828;

height: 100vh;

background: #eee;

display: inline;

}.versionPaneOpen {

display: none;

}

來,測試一下

你應至少保存一個版本,因此請單擊「Version History」按鈕以切換窗格的打開和關閉狀態。它很醜,但很管用。

我們需要做的最後一件事是彈出一個模式來顯示歷史版本的內容。讓我們通過添加一個名為handleVersionModal的函數來解決這個問題。

handleVersionModal = (hash) => {

const { userSession } = this.state;

this.setState({ selectedVersionContent: "", versionModal: true });

fetch(`https://gateway.pinata.cloud/ipfs/${hash}`)

.then(function(response) {

return response.json();

})

.then(function(myJson) {

const encryptedContent = myJson.pinnedContent;

const decryptedContent = userSession.decryptContent(JSON.parse(encryptedContent), {privateKey: userSession.loadUserData().appPrivateKey});

this.setState({ selectedVersionContent: JSON.parse(decryptedContent)});

});

}

我們使用JavaScript本機Fetch API來處理調用IPFS網關,以獲取特定於我們在版本窗格中選擇的版本的內容。該內容已加密,需要正確解析和解密才能訪問。

但是,如果你控制臺記錄decryptedContent變量,你將看到正在獲取相關版本的內容。我們將該內容設置為selectedVersionContent狀態變量並將versionModal設置為true。

讓我們將這一切用於在屏幕上呈現過去的版本。在你之前編寫的版本頁面JSX下面,添加以下內容:

<div className={versionModal ? "versionModalOpen" : "versionModalClosed"}> <span onClick={() => this.setState({versionModal: false})} id="version-close">Close</span> { selectedVersionContent ? <div dangerouslySetInnerHTML={{__html: selectedVersionContent}} />: <h3>Loading content for selected version...</h3> }</div>

現在,我們需要設計一些可管理的樣式。在App.css中,添加以下內容:

.versionModalOpen { display: inline; position: fixed; text-align: left; left: 12.5%; top: 15%; width: 75%; min-height: 500px; margin: auto; z-index: 999; background: #eee; padding: 25px; border: 1px solid #282828; border-radius: 3px;}.versionModalClosed { display: none;}#version-close { position: relative; right: 10px; top: 10px; z-index: 1000; cursor: pointer;}

現在試試吧

打開「版本歷史」窗格,單擊過去的版本,應該彈出一個模式,其中包含該版本的內容供你查看。

你現在可以擁有note-taking記錄系統,同時通過版本歷史保留對所有過去迭代控制。最重要的是,每個版本的筆記都由你控制的私鑰加密。

—end—

本文由IPFS原力區編譯,原文連結:

https://hackernoon.com/tutorial-build-a-versioning-system-on-ipfs-77lvx2geh

【IPFS原力區】

價值觀:價值 共建 共享 榮耀

總部位於上海,聚集基於分布式網絡&存儲的眾多技術大咖和愛好者,深耕基於 IPFS的商業生態建設和社區發展。

每周二舉辦「分布式存儲網絡」主題沙龍,聚集了眾多技術大咖和 IPFS 愛好者,通過持續輸出全面、精細、優質的IPFS諮詢和技術支持,將生態中的愛好者轉化為IPFS支持者和參與者,共建IPFS生態的健康發展。

相關焦點

  • [IPFS周報101]用IPFS構建最酷的東西!
    在世界標準時間4:30參加決賽演示,查看入圍者可以使用IPFS和Filecoin創建哪些內容,以及進行評審等等!Pinata的新功能Pinata的夥伴們再次做到了,查看他們最新的教程,內容涉及如何使用Unity,Pinata的Pin Explorer,Rarible和IPFS創建和出售統一預製件
  • IPFS官方周報第97期:抖音國際版被下架?IPFS可提供解決方案!
    星際文件系統 (IPFS) ( https://ipfs.io/ )是一種新的超媒體分布協議,通過內容和標識來尋址。IPFS 支持創建完全分布式的應用程式。它的目標是使網絡更快、更安全、更開放。以下是自上次 IPFS 周報以來的一些亮點。
  • IPFS官方周報89期,託管維基百科|螢火蟲Filecoin礦機
    Testground試驗場引入Testground v0.5(試驗場v0.5版本)。進行大規模測試、基準測試和模擬分布式和p2p系統的平臺。對於我們和整個p2p生態系統來說,這都是一個巨大的裡程碑。集線器是連接和擴展IPFS、libp2p和Filecoin的庫和服務的集合,因此可以它構建應用程式、保護數據!在Textile博客上了解關於集線器的所有信息。
  • 「Filecoin星際大陸」JS-ipfs為瀏覽器實現提供無限可能
    今天小編要跟大家介紹另一個使用IPFS構建的實現——JS-ipfs。JS-ipfs是什麼?JS-ipfs是指完全用JavaScript編寫的完整P2P協議,可以運行在瀏覽器、Service Worker、Web擴展和Node.js中,它打開了通向無限可能世界的大門。JS-ipfs當前已經更新至0.47.0版本,官網數據顯示最近一個月的下載量已達25,151。
  • ipfs是什麼礦機靠譜嗎?ipfs是什麼?
    ipfs近在礦工市場的關注度一路飆升,越來越多的技術盲也開始逐漸關注IPFS,但是大部分人只是僅僅聽過這個名詞,實際上對於IPFS及ipfs礦機還很陌生。IPFS本質上是一種內容可尋址、版本化、點對點超媒體的分布式存儲、傳輸協議,目標是補充甚至取代過去20年裡使用的超文本媒體傳輸協議(HTTP)。
  • ipfs全領域應用 那些用了ipfs的平臺 有什麼不一樣?
    同時,它還附帶了基於區塊鏈的激勵系統,用戶在不可變的STEEM區塊鏈中分享或評論,可以獲得加密的通行證。同樣,上傳視頻也使用STEEM區塊鏈來激勵平臺用戶創建視頻。它的目標是取代youtube。目前,Dtube 正在「GUN資料庫」上運行一個完全分散去中心化的消息系統。它還為玩家打開了實時視頻流的功能,努力使其去中心化。
  • IPFS分布式存儲的落地應用
    基於IPFS搭建的APP、應用和平臺等,正在重構整個網際網路。ipfs基於p2p網絡並進行優化,迅雷適用的場景,ipfs同樣適用!隨著技術的成熟,目前IPFS生態系統中已經衍生出了較多的dapp、工具和項目,其中包括Brave,3box,EthDNS等新來者。
  • 「Filecoin星際大陸」Berty:gomobile + IPFS的發展歷程
    我們首先製作了一個簡單的PoC,用於使用UI的React-Native和使用gomobile編譯的go-ipfs節點來顯示IPFS WebUI。初始版本在Protocol Labs 的Steven Allen的幫助下,我們迅速實施了gomobile-ipfs的第一個版本,他詳細審查了我們的代碼並幫助我們解決了許多問題。然後,我們的倉庫迅速移至IPFS船廠並進行了開源。
  • 「怪盜」IPFS和Filecoin憑什麼能讓全世界投資者瘋狂?
    基於IPFS技術開發的應用也不斷出現,IPFS直接整合至Brave瀏覽器中,將 Hadoop 置於IPFS之上進行p2p數據分析,PeerPad利用IPFS構建無伺服器、實時的、離線協作式應用等。陸續與微軟、美國宇航局(NASA)等知名機構、企業建立合作關係,進一步深化了實際應用價值。
  • 中鏈雲 | IPFS入門基礎知識續更(六)
    點對點:定位內容和協調交付的協議;可以在本地系統上安裝一個IPFS的文件系統;可以像訪問本地系統一樣訪問遠程資源;提供網絡功能的模塊化方法,如路由和虛擬電路;無需伺服器的文件點對點傳輸;基於公鑰基礎設施(PKI)的全局命名空間;確保文件的完整性和版本控制的系統;多元化的瀏覽器
  • ipfs去中心存儲礦機成新商機,IPFS存儲技術將是下個千億商機市場嗎?
    因此,它吸引了許多網民使用,優點是資源發布者不需要具有高性能的伺服器即可將發布的資源快速有效地轉移到其他BT客戶端軟體用戶。那是。而且大多數BT軟體都是免費的。 精神是「每個人都對我來說,我就是每個人」。您可以將文件作為種子供他人下載。您還可以下載其他人提供的種子。您下載的人越多,您增長的人就越多。這樣,每個人都會更快。
  • js-ipfs, go-libp2p和js-libp2p的新版本
    訂閱英文原版IPFS周報 :https://blog.ipfs.io/weekly-114/歡迎來到IPFS周報以下是自上期IPFS周報以來的一些亮點js-ipfs,go-libp2p和js-libp2p的新版本是時候進行一些更新了!
  • 使用Unity構建和移植移動遊戲
    由於各個平臺使用的渲染處理器千差萬別,資產在藝術工具中的呈現結果,與在給定平臺上遊戲中的呈現結果往往存在脫節。此外,平臺的複雜性越來越高,為了支持所有這些子系統,代碼庫在不斷擴大,導致編譯和構建的時間冗長。
  • 關於IPFS/Filecoin購買礦機的攻略丨Ipfs挖礦丨星際數據
    IPFS與HTTP都是出自斯坦福實驗室,中心化的HTTP目前已經無法適應5G時代下大數據的發展需求,IPFS顛覆式提出以去中心化的網絡協議來構建未來網絡世界,在IPFS系統中,Filecoin是官方利用區塊鏈為基礎結構搭建的通證經濟與激勵機制,IPFS領域生態服務商(星際數據)通過接取IPFS官方分發的數據存儲業務,可以獲得加密貨幣Filecoin獎勵。
  • IPFS官方周報-87期
    星際文件系統(IPFS)是一種新的超媒體分發協議,通過內容和身份進行尋址。IPFS支持創建完全分布式應用程式,它的目的是使網絡更快、更安全、更開放。快速回顧一下我們在星際文件系統星系中及其他領域共同完成的工作!
  • 使用魔方網表數字中臺構建無感知管理系統
    這種操作就是那麼自然,與普通管理系統完全不同,不需要打開一個特別大菜單,一級一級去點開,去找所需的功能模塊。也不用專門登錄,不需要佔用大家大量時間操作、學習。數位化時代,企業中使用的系統越來越多,構建無感知管理系統價值越來越大。而構建此類系統可以使用魔方網表,它為華為公司打造的無感知「問題追蹤」系統是一個鮮活的例證。
  • 使用Amazon SageMaker 構建基於 gluon 的推薦系統
    而結果也顯示,使用這種算法優化,對於當時亞馬遜的業績提升起到了很大作用。今天,隨著電子商務規模的不斷擴大,商品種類快速增長,顧客需要花費大量的時間才能找到自己想買的商品。這種瀏覽大量無關信息和產品的過程無疑會使淹沒在信息過載問題中的消費者不斷流失。為了解決這些問題,個性化推薦系統應運而生。
  • IPFS官方資訊:升空第3天回顧:區塊鏈生態系統的聖杯丨星際數據
    我們聽到了整個Web3生態系統的開發人員的聲音,包括以太坊聯合創始人Joe Lubin,Filecoin產品負責人Pooja Shah,Infura高級工程師Tim Myers,Aave執行長Stani Kulechov和Ren CTO Loong Wang。以下是當今「在Filecoin上構建」議程中的一些要點,以及明天要存儲的內容的快照。
  • 智能家居系統選購指南
    我國安防市場推出的智能家居產品很多,加之我國沒有統一技術規範,導致國內和國外的品牌眾多,其產品都有各自的優缺點,採購者在選擇適合自己的產品的時候往往難以進行比較,因此在選購過程中應當從多角度、多維度去做綜合考量,以選到合適的智能家居系統,下面旺龍智能將為大家提供一些選購指南,希望對大家有所幫助
  • 「臨沂健康易通行」APP和二維碼操作指南
    「臨沂健康易通行」APP和二維碼操作指南 2020-03-09 22:43 來源:澎湃新聞·澎湃號·政務