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生態的健康發展。