Use Gist as a new synchronization method

This commit is contained in:
suziwen 2024-08-03 15:15:20 +08:00
parent 3994518c00
commit 6f4a579c18
17 changed files with 679 additions and 112 deletions

View File

@ -42,23 +42,24 @@ class LocalStorage {
}
const instance = new LocalStorage()
if (!globalThis.localStorage) {
globalThis.localStorage = new Proxy(instance, {
set: function (obj, prop, value) {
if (LocalStorage.prototype.hasOwnProperty(prop)) {
instance[prop] = value
} else {
instance.setItem(prop, value)
}
return true
},
get: function (target, name) {
if (LocalStorage.prototype.hasOwnProperty(name)) {
return instance[name]
}
if (valuesMap.has(name)) {
return instance.getItem(name)
}
globalThis.zeroLocalStorage = new Proxy(instance, {
set: function (obj, prop, value) {
if (LocalStorage.prototype.hasOwnProperty(prop)) {
instance[prop] = value
} else {
instance.setItem(prop, value)
}
})
return true
},
get: function (target, name) {
if (LocalStorage.prototype.hasOwnProperty(name)) {
return instance[name]
}
if (valuesMap.has(name)) {
return instance.getItem(name)
}
}
})
if (!globalThis.localStorage) {
globalThis.localStorage = globalThis.zeroLocalStorage;
}

View File

@ -1,7 +1,5 @@
const logStore = idbKeyval.createStore('log-store', 'log-store');
const dayOfWeek = moment().format('E') // Day of Week (ISO), keep logs max 7 day
const logKey = 'zerolog-' + dayOfWeek
const logSequence = []
let isRunning = false
let splitStr = '\n------------------\n'
@ -12,15 +10,21 @@ const originConsoleError = console.error
const _logFn = async function(){
if (isRunning) return
isRunning = true
const _moment = moment()
const dayOfWeek = _moment.format('E') // Day of Week (ISO), keep logs max 7 day
const monthNum = _moment.format('DD')
const logKey = 'zerolog-' + dayOfWeek
while (logSequence.length > 0) {
const str = logSequence.join('\n');
logSequence.length = 0;
let logInfo = await idbKeyval.get(logKey, logStore)
let date = _moment.format('YYYY-MM-DD')
if (!logInfo || !logInfo.date) {
logInfo = { date: moment().format('YYYY-MM-DD'), val: ''}
logInfo = { date: date, val: ''}
}
let { date, val } = logInfo
if ( !date.endsWith(dayOfWeek)) {
let { val } = logInfo
if ( logInfo.date != date) {
val = ''
}
val += splitStr
@ -43,6 +47,9 @@ const replacerFn = (key, value)=>{
case 'password':
case 'host':
case 'port':
case 'token':
case 'gistToken':
case 'gistId':
return '<secret>'
default:
return value
@ -84,7 +91,7 @@ const _lastErrorLogFn = async ()=>{
_lastErrorLogFn.isRunning = false
}
const lastErrorLogFn = async ()=>{
const lastErrorLogFn = async function (){
const val = getStr.apply(null, arguments)
_lastErrorLogFn.val = val
_lastErrorLogFn()

View File

@ -154,13 +154,11 @@ actionForUrl = (url) ->
storage = new OmegaTargetCurrent.Storage('local')
state = new OmegaTargetCurrent.BrowserStorage(localStorage, 'omega.local.')
state = new OmegaTargetCurrent.BrowserStorage(zeroLocalStorage, 'omega.local.')
if chrome?.storage?.sync or browser?.storage?.sync
syncStorage = new OmegaTargetCurrent.Storage('sync')
syncStorage = new OmegaTargetCurrent.SyncStorage('sync', state)
sync = new OmegaTargetCurrent.OptionsSync(syncStorage)
if localStorage['omega.local.syncOptions'] != '"sync"'
sync.enabled = false
sync.transformValue = OmegaTargetCurrent.Options.transformValueForSync
proxyImpl = OmegaTargetCurrent.proxy.getProxyImpl(Log)
@ -315,10 +313,23 @@ refreshActivePageIfEnabled = ->
else
chrome.tabs.reload(tabs[0].id, {bypassCache: true})
resetAllOptions = ->
options.ready.then ->
options._watchStop?()
options._syncWatchStop?()
Promise.all([
chrome.storage.sync.clear(),
chrome.storage.local.clear()
])
chrome.runtime.onMessage.addListener (request, sender, respond) ->
return unless request and request.method
options.ready.then ->
if request.method == 'getState'
if request.method == 'resetAllOptions'
target = globalThis
method = resetAllOptions
else if request.method == 'getState'
target = state
method = state.get
else if request.method == 'setState'

View File

@ -1,7 +1,13 @@
logStore = idbKeyval.createStore('log-store', 'log-store')
syncStore = idbKeyval.createStore('sync-store', 'sync')
isProcessing = false
waitTimeFn = (timeout = 1000) ->
return new Promise((resolve, reject) ->
setTimeout( ->
resolve()
, timeout)
)
window.OmegaDebug =
getProjectVersion: ->
@ -9,8 +15,6 @@ window.OmegaDebug =
getExtensionVersion: ->
chrome.runtime.getManifest().version
downloadLog: ->
return if isProcessing
isProcessing = true
idbKeyval.entries(logStore).then((entries) ->
zip = new JSZip()
zipFolder = zip.folder('ZeroOmega')
@ -28,26 +32,30 @@ window.OmegaDebug =
).then((blob) ->
filename = "ZeroOmegaLog_#{Date.now()}.zip"
saveAs(blob, filename)
isProcessing = false
)
resetOptions: ->
return if isProcessing
isProcessing = true
Promise.all([
idbKeyval.clear(logStore),
chrome.storage.local.clear()
]).then( ->
chrome.runtime.sendMessage({
method: 'resetAllOptions'
}, (response) ->
# firefox still use localStorage
localStorage.clear()
# Prevent options loading from sync storage after reload.
localStorage['omega.local.syncOptions'] = '"conflict"'
isProcessing = false
chrome.runtime.reload()
# as storage watch value changed
# and background localStorage state delay saved
# this must after storage and wait 2 seconds
Promise.all([
idbKeyval.clear(logStore),
idbKeyval.clear(syncStore),
waitTimeFn(2000)
]).then( ->
idbKeyval.clear()
).then( ->
# Prevent options loading from sync storage after reload.
#localStorage['omega.local.syncOptions'] = '"conflict"'
chrome.runtime.reload()
)
)
reportIssue: ->
return if isProcessing
isProcessing = true
idbKeyval.get('lastError', logStore).then((lastError) ->
isProcessing = false
url = 'https://github.com/suziwen/ZeroOmega/issues/new?title=&body='
finalUrl = url
try

View File

@ -158,7 +158,8 @@ angular.module('omegaTarget', []).factory 'omegaTarget', ($q) ->
chrome.tabs.create url: 'chrome://extensions/configureCommands'
setOptionsSync: (enabled, args) ->
callBackground('setOptionsSync', enabled, args)
resetOptionsSync: (enabled, args) -> callBackground('resetOptionsSync')
resetOptionsSync: (args) -> callBackground('resetOptionsSync', args)
checkOptionsSyncChange: -> callBackground('checkOptionsSyncChange')
setRequestInfoCallback: (callback) ->
requestInfoCallback = callback

View File

@ -1,5 +1,6 @@
module.exports =
Storage: require('./storage')
SyncStorage: require('./sync_storage')
Options: require('./options')
ChromeTabs: require('./tabs')
SwitchySharp: require('./switchysharp')

View File

@ -0,0 +1,384 @@
OmegaTarget = require('omega-target')
Promise = OmegaTarget.Promise
onChangedListenerInstalled = false
isPulling = false
isPushing = false
state = null
optionFilename = 'ZeroOmega.json'
gistId = ''
gistToken = ''
gistHost = 'https://api.github.com'
processCheckCommit = ->
getLastCommit(gistId).then((remoteCommit) ->
state.set({
'lastGistSync': Date.now()
}).then(->
state.get({'lastGistCommit': '-2'}).then(({ lastGistCommit }) ->
return lastGistCommit isnt remoteCommit
)
)
).catch( ->
return true
)
processPull = (syncStore) ->
return new Promise((resolve, reject) ->
getGist(gistId).then((gist) ->
if isPushing
resolve({changes: {}})
else
changes = {}
getAll(syncStore).then((data) ->
try
optionsStr = gist.files[optionFilename]?.content
options = JSON.parse(optionsStr)
for own key, val of data
changes[key] = {
oldValue: val
}
for own key, val of options
target = changes[key]
unless target
changes[key] = {}
target = changes[key]
target.newValue = val
for own key,val of changes
if JSON.stringify(val.oldValue) is JSON.stringify(val.newValue)
delete changes[key]
catch e
changes = {}
state?.set({
'lastGistCommit': gist.history[0]?.version
'lastGistState': 'success'
'lastGistSync': Date.now()
})
resolve({
changes: changes,
remoteOptions: options
})
)
).catch((e) ->
state?.set({
'lastGistSync': Date.now()
'lastGistState': 'fail: ' + e
})
resolve({changes: {}})
)
)
getAll = (syncStore) ->
idbKeyval.entries(syncStore).then((entries) ->
data = {}
entries.forEach((entry) ->
data[entry[0]] = entry[1]
)
return data
)
_processPush = ->
if processPush.sequence.length > 0
# syncStore = processPush.sequence.shift()
syncStore = processPush.sequence[processPush.sequence.length - 1]
processPush.sequence.length = 0
getAll(syncStore).then((data) ->
updateGist(gistId, data)
).then( ->
_processPush()
)
else
isPushing = false
processPush = (syncStore) ->
processPush.sequence.push(syncStore)
return if isPushing
isPushing = true
_processPush()
processPush.sequence = []
getLastCommit = (gistId) ->
fetch(gistHost + '/gists/' + gistId + '/commits?per_page=1', {
headers: {
"Accept": "application/vnd.github+json"
"Authorization": "Bearer " + gistToken
"X-GitHub-Api-Version": "2022-11-28"
}
}).then((res) -> res.json()).then((data) ->
if data.message
throw data.message
return data[0]?.version
)
getGist = (gistId) ->
#curl -L \
# -H "Accept: application/vnd.github+json" \
# -H "Authorization: Bearer <YOUR-TOKEN>" \
# -H "X-GitHub-Api-Version: 2022-11-28" \
# https://api.github.com/gists/GIST_ID
fetch(gistHost + '/gists/' + gistId, {
headers: {
"Accept": "application/vnd.github+json"
"Authorization": "Bearer " + gistToken
"X-GitHub-Api-Version": "2022-11-28"
}
}).then((res) -> res.json()).then((data) ->
if data.message
throw data.message
return data
)
updateGist = (gistId, options) ->
postBody = {
description: 'ZeroOmega Sync'
files: {}
}
postBody.files[optionFilename] = {
content: JSON.stringify(options, null, 4)
}
fetch(gistHost + '/gists/' + gistId, {
headers: {
"Accept": "application/vnd.github+json"
"Authorization": "Bearer " + gistToken
"X-GitHub-Api-Version": "2022-11-28"
}
"method": "PATCH"
body: JSON.stringify(postBody)
}).then((res) ->
res.json()
).then((data) ->
if data.message
throw data.message
state?.set({
'lastGistCommit': data.history[0]?.version
'lastGistState': 'success'
'lastGistSync': Date.now()
})
return data
).catch((e) ->
state?.set({
'lastGistState': 'fail: ' + e
'lastGistSync': Date.now()
})
console.error('update gist fail::', e)
)
class ChromeSyncStorage extends OmegaTarget.Storage
@parseStorageErrors: (err) ->
if err?.message
sustainedPerMinute = 'MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE'
if err.message.indexOf('QUOTA_BYTES_PER_ITEM') >= 0
err = new OmegaTarget.Storage.QuotaExceededError()
err.perItem = true
else if err.message.indexOf('QUOTA_BYTES') >= 0
err = new OmegaTarget.Storage.QuotaExceededError()
else if err.message.indexOf('MAX_ITEMS') >= 0
err = new OmegaTarget.Storage.QuotaExceededError()
err.maxItems = true
else if err.message.indexOf('MAX_WRITE_OPERATIONS_') >= 0
err = new OmegaTarget.Storage.RateLimitExceededError()
if err.message.indexOf('MAX_WRITE_OPERATIONS_PER_HOUR') >= 0
err.perHour = true
else if err.message.indexOf('MAX_WRITE_OPERATIONS_PER_MINUTE') >= 0
err.perMinute = true
else if err.message.indexOf(sustainedPerMinute) >= 0
err = new OmegaTarget.Storage.RateLimitExceededError()
err.perMinute = true
err.sustained = 10
else if err.message.indexOf('is not available') >= 0
# This could happen if the storage area is not available. For example,
# some Chromium-based browsers disable access to the sync storage.
err = new OmegaTarget.Storage.StorageUnavailableError()
else if err.message.indexOf(
'Please set webextensions.storage.sync.enabled to true') >= 0
# This happens when sync storage is disabled in flags.
err = new OmegaTarget.Storage.StorageUnavailableError()
return Promise.reject(err)
constructor: (@areaName, _state) ->
state = _state
syncStore = idbKeyval.createStore('sync-store', 'sync')
@syncStore = syncStore
get = (key) ->
return new Promise((resolve, reject) ->
getAll(syncStore).then((data) ->
result = {}
if Array.isArray(key)
key.forEach( _key ->
result[_key] = data[_key]
)
else if key is null
result = data
else
result[key] = data[key]
resolve(result)
)
)
set = (record) ->
return new Promise((resolve, reject) ->
try
if !record or typeof record isnt 'object' or Array.isArray(record)
throw new SyntaxError(
'Only Object with key value pairs are acceptable')
entries = []
for own key, value of record
entries.push([key, value])
idbKeyval.setMany(entries, syncStore).then( ->
processPush(syncStore)
resolve(record)
)
catch e
reject(e)
)
_remove = (key) ->
if Array.isArray(key)
Promise.resolve(idbKeyval.delMany(key, syncStore))
else
Promise.resolve(idbKeyval.del(key, syncStore))
remove = (key) ->
Promise.resolve(_remove(key).then( ->
processPush(syncStore)
return
))
clear = ->
Promise.resolve(idbKeyval.clear(syncStore).then(->
processPush(syncStore)
return
))
@storage =
get: get
set: set
remove: remove
clear: clear
get: (keys) ->
keys ?= null
Promise.resolve(@storage.get(keys))
.catch(ChromeSyncStorage.parseStorageErrors)
set: (items) ->
if Object.keys(items).length == 0
return Promise.resolve({})
Promise.resolve(@storage.set(items))
.catch(ChromeSyncStorage.parseStorageErrors)
remove: (keys) ->
if not keys?
return Promise.resolve(@storage.clear())
if Array.isArray(keys) and keys.length == 0
return Promise.resolve({})
Promise.resolve(@storage.remove(keys))
.catch(ChromeSyncStorage.parseStorageErrors)
flush: ({data}) ->
entries = []
result = null
if data and data.schemaVersion
for own key, value of data
entries.push([key, value])
result = idbKeyval.setMany(entries, @syncStore)
Promise.resolve(result)
##
# param(withRemoteData) retrive gist file content
##
init: (args) ->
gistId = args.gistId
gistToken = args.gistToken
return new Promise((resolve, reject) ->
getLastCommit(gistId).then( (lastGistCommit) ->
if args.withRemoteData
getGist(gistId).then((gist) ->
try
optionsStr = gist.files[optionFilename].content
options = JSON.parse(optionsStr)
resolve({options, lastGistCommit})
catch e
resolve({})
)
else
resolve({})
).catch((e) ->
reject(e)
)
)
##
# param (opts) opts.immediately , immediately update changed
# param (opts) opts.force, force get remote content
##
checkChange: (opts = {}) ->
isPulling = true
processCheckCommit().then((isChanged) =>
if isChanged or opts.force
processPull(@syncStore).then(({changes, remoteOptions}) =>
@flush({data: remoteOptions}).then( =>
isPulling = false
ChromeSyncStorage.onChangedListener(changes, @areaName, opts)
)
)
else
console.log('no changed')
isPulling = false
)
watch: (keys, callback) ->
chrome.alarms.create('omega.syncCheck', {
periodInMinutes: 5
})
ChromeSyncStorage.watchers[@areaName] ?= {}
area = ChromeSyncStorage.watchers[@areaName]
watcher = {keys: keys, callback: callback}
enableSync = true
id = Date.now().toString()
while area[id]
id = Date.now().toString()
if Array.isArray(keys)
keyMap = {}
for key in keys
keyMap[key] = true
keys = keyMap
area[id] = {keys: keys, callback: callback}
if not onChangedListenerInstalled
# chrome alerm
@checkChange()
chrome.alarms.onAlarm.addListener (alarm) =>
return unless enableSync
return if isPulling
switch alarm.name
when 'omega.syncCheck'
@checkChange()
#chrome.storage.onChanged.addListener(ChromeSyncStorage.onChangedListener)
onChangedListenerInstalled = true
return ->
enableSync = false
delete area[id]
##
# param (opts) opts.immediately , immediately update changed
##
@onChangedListener: (changes, areaName, opts = {}) ->
map = null
for _, watcher of ChromeSyncStorage.watchers[areaName]
match = watcher.keys == null
if not match
for own key of changes
if watcher.keys[key]
match = true
break
if match
if not map?
map = {}
for own key, change of changes
map[key] = change.newValue
watcher.callback(map, opts)
@watchers: {}
module.exports = ChromeSyncStorage

View File

@ -59,9 +59,9 @@ class BrowserStorage extends Storage
else
index = 0
while true
key = @proto.key.call(index)
key = @proto.key.call(@storage, index)
break if key == null
if @key.substr(0, @prefix.length) == @prefix
if key.substr(0, @prefix.length) == @prefix
@proto.removeItem.call(@storage, @prefix + keys)
else
index++

View File

@ -4,7 +4,7 @@ Log = require './log'
replacer = (key, value) ->
switch key
# Hide values for a few keys with privacy concerns.
when "username", "password", "host", "port"
when "username", "password", "host", "port", "token", "gistToken", "gistId"
return "<secret>"
else
value

View File

@ -45,6 +45,8 @@ class Options
# @returns {{}} The transformed value
###
@transformValueForSync: (value, key) ->
if key is '-customCss'
return undefined
if key[0] == '+'
if OmegaPac.Profiles.updateUrl(value)
profile = {}
@ -83,22 +85,36 @@ class Options
@_watchStop = null
loadRaw = if options? then Promise.resolve(options) else
if not @sync?.enabled
if not @sync?
@_state.set({'syncOptions': 'unsupported'})
if not @sync?
@_state.set({'syncOptions': 'unsupported'})
@_storage.get(null)
else
@_state.set({'syncOptions': 'sync'})
@_syncWatchStop = @sync.watchAndPull(@_storage)
@sync.copyTo(@_storage).catch(Storage.StorageUnavailableError, =>
console.error('Warning: Sync storage is not available in this ' +
'browser! Disabling options sync.')
@_syncWatchStop?()
@_syncWatchStop = null
@sync = null
@_state.set({'syncOptions': 'unsupported'})
).then =>
@_storage.get(null)
@_state.get({
'syncOptions': ''
'gistId': ''
'gistToken': ''
}).then(({syncOptions, gistId, gistToken}) =>
unless gistId
syncOptions = 'pristine'
@_state.set({'syncOptions': 'pristine'})
@sync.enabled = syncOptions is 'sync'
unless @sync.enabled
@_storage.get(null)
else
@sync.init({gistId, gistToken}).catch((e) ->
console.error('sync init fail::', e)
)
@_syncWatchStop = @sync.watchAndPull(@_storage)
@sync.copyTo(@_storage).catch(Storage.StorageUnavailableError, =>
console.error('Warning: Sync storage is not available in this ' +
'browser! Disabling options sync.')
@_syncWatchStop?()
@_syncWatchStop = null
@sync = null
@_state.set({'syncOptions': 'unsupported'})
).then =>
@_storage.get(null)
)
@optionsLoaded = loadRaw.then((options) =>
@upgrade(options)
@ -235,7 +251,7 @@ class Options
# Current schemaVersion.
Promise.resolve([options, changes])
else
Promise.reject new Error("Invalid schemaVerion #{version}!")
Promise.reject new Error("Invalid schemaVersion #{version}!")
###*
# Parse options in various formats (including JSON & base64).
@ -1004,53 +1020,80 @@ class Options
# @param {boolean=false} args.force If true, overwrite options when conflict
# @returns {Promise} A promise which is fulfilled when the syncing is switched
###
setOptionsSync: (enabled, args) ->
setOptionsSync: (enabled, args = {}) ->
@log.method('Options#setOptionsSync', this, arguments)
if not @sync?
return Promise.reject(new Error('Options syncing is unsupported.'))
@_state.get({'syncOptions': ''}).then ({syncOptions}) =>
@_state.get({
'syncOptions': '', lastGistCommit: ''
}).then ({syncOptions, lastGistCommit}) =>
if not enabled
if syncOptions == 'sync'
@_state.set({'syncOptions': 'conflict'})
@_state.set({'syncOptions': 'pristine'})
@sync.enabled = false
@_syncWatchStop?()
@_syncWatchStop = null
return
if syncOptions == 'conflict'
if not args?.force
if not args.force
return Promise.reject(new Error(
'Syncing not enabled due to conflict. Retry with force to overwrite
local options and enable syncing.'))
return if syncOptions == 'sync'
@_state.set({'syncOptions': 'sync'}).then =>
if syncOptions == 'conflict'
# Try to re-init options from sync.
@sync.enabled = false
@_storage.remove().then =>
@sync.enabled = true
@init()
else
@sync.enabled = true
@_syncWatchStop?()
@sync.requestPush(@_options)
@_syncWatchStop = @sync.watchAndPull(@_storage)
return
{ gistId, gistToken } = args
@sync.init({
gistId, gistToken, withRemoteData: true
}).then( ({
options: remoteOptions, lastGistCommit: remoteLastGistCommit
}) =>
@_state.set({
'syncOptions': 'sync'
'gistId': gistId
'gistToken': gistToken
}).then =>
if syncOptions == 'conflict'
# Try to re-init options from sync.
@sync.enabled = false
@_storage.remove().then =>
@sync.enabled = true
@init()
else
if remoteOptions.schemaVersion
@sync.flush({data: remoteOptions}).then( =>
@sync.enabled = false
@_state.set({'syncOptions': 'conflict'})
return
)
else
@sync.enabled = true
@_syncWatchStop?()
@sync.requestPush(@_options)
@_syncWatchStop = @sync.watchAndPull(@_storage)
return
)
###*
# Clear the sync storage, resetting syncing state to pristine.
# @returns {Promise} A promise which is fulfilled when the syncing is reset.
###
resetOptionsSync: ->
resetOptionsSync: (args) ->
@log.method('Options#resetOptionsSync', this, arguments)
if not @sync?
return Promise.reject(new Error('Options syncing is unsupported.'))
@sync.enabled = false
@_syncWatchStop?()
@_syncWatchStop = null
@_state.set({'syncOptions': 'conflict'})
return @sync.storage.remove().then =>
@_state.set({'syncOptions': 'conflict'}).then( =>
@sync.init(args)
).then( =>
@sync.storage.remove()
).then( =>
@_state.set({'syncOptions': 'pristine'})
)
checkOptionsSyncChange: ->
if @sync and @sync.enabled
@sync.checkChange()
module.exports = Options

View File

@ -205,10 +205,22 @@ class OptionsSync
@_logOperations('OptionsSync::pull', operations)
local.apply(operations)
@storage.watch null, (changes) =>
@storage.watch null, (changes, opts = {}) =>
for own key, value of changes
pull[key] = value
return if pullScheduled?
pullScheduled = setTimeout(doPull, @pullThrottle)
if opts.immediately
doPull()
else
pullScheduled = setTimeout(doPull, @pullThrottle)
checkChange: ->
@storage.checkChange({
immediately: true
force: true
})
init: (args) ->
@storage.init(args)
flush: ({data}) ->
@storage.flush({data})
module.exports = OptionsSync

View File

@ -172,7 +172,7 @@ fieldset[disabled] .form-control {
position: relative;
}
.alert-success {
color: var(--primaryColor);
color: var(--positiveColor);
background-color: transparent;
border-color: var(--lighterBackground);
position: relative;
@ -185,7 +185,7 @@ fieldset[disabled] .form-control {
position: absolute;
inset: 0;
opacity: 0.1;
background-color: var(--primaryColor);
background-color: var(--positiveColor);
pointer-events: none;
}
@ -429,3 +429,9 @@ main .page-header {
.sp-palette-container {
border-right-color: var(--selectionBackground);
}
.input-group-addon{
color: var(--defaultForeground);
background-color: var(--lighterBackground);
border-color: var(--lighterBackground);
}

View File

@ -1,12 +1,25 @@
angular.module('omega').controller 'AboutCtrl', ($scope, $rootScope,
$modal, omegaDebug) ->
$scope.downloadLog = omegaDebug.downloadLog
$scope.reportIssue = omegaDebug.reportIssue
angular.module('omega').controller 'AboutCtrl', (
$scope, $rootScope,$modal, omegaDebug
) ->
$scope.downloadLog = ->
$scope.logDownloading = true
Promise.resolve(omegaDebug.downloadLog()).then( ->
$scope.logDownloading = false
)
$scope.reportIssue = ->
$scope.issueReporting = true
omegaDebug.reportIssue().then( ->
$scope.issueReporting = false
)
$scope.showResetOptionsModal = ->
$modal.open(templateUrl: 'partials/reset_options_confirm.html').result
.then -> omegaDebug.resetOptions()
$modal
.open(templateUrl: 'partials/reset_options_confirm.html').result
.then ->
$scope.optionsReseting = true
omegaDebug.resetOptions().then( ->
$scope.optionsReseting = false
)
try
$scope.version = omegaDebug.getProjectVersion()

View File

@ -1,9 +1,22 @@
angular.module('omega').controller 'IoCtrl', ($scope, $rootScope,
$window, $http, omegaTarget, downloadFile) ->
angular.module('omega').controller 'IoCtrl', (
$scope, $rootScope, $window, $http, omegaTarget, downloadFile
) ->
omegaTarget.state('web.restoreOnlineUrl').then (url) ->
omegaTarget.state([
'web.restoreOnlineUrl',
'gistId',
'gistToken',
'lastGistSync',
'lastGistState'
]).then ([url, gistId, gistToken, lastGistSync, lastGistState]) ->
if url
$scope.restoreOnlineUrl = url
if gistId
$scope.gistId = gistId
if gistToken
$scope.gistToken = gistToken
$scope.lastGistSync = new Date(lastGistSync or Date.now())
$scope.lastGistState = lastGistState or ''
$scope.exportOptions = ->
$rootScope.applyOptionsConfirm().then ->
@ -57,21 +70,59 @@ angular.module('omega').controller 'IoCtrl', ($scope, $rootScope,
), $scope.downloadError).finally ->
$scope.restoringOnline = false
$scope.enableOptionsSync = (args) ->
$scope.enableOptionsSync = (args = {}) ->
enable = ->
omegaTarget.setOptionsSync(true, args).finally ->
if !$scope.gistId or !$scope.gistToken
$rootScope.showAlert(
type: 'error'
message: 'Gist Id or Gist Token is required'
)
return
args.gistId = $scope.gistId
args.gistToken = $scope.gistToken
$scope.enableOptionsSyncing = true
omegaTarget.setOptionsSync(true, args).then( ->
$window.location.reload()
).catch((e) ->
$scope.enableOptionsSyncing = false
$rootScope.showAlert(
type: 'error'
message: e + ''
)
console.log('error:::', e)
)
if args?.force
enable()
else
$rootScope.applyOptionsConfirm().then enable
$scope.checkOptionsSyncChange = ->
$scope.enableOptionsSyncing = true
omegaTarget.checkOptionsSyncChange().then( ->
$window.location.reload()
)
$scope.disableOptionsSync = ->
omegaTarget.setOptionsSync(false).then ->
$rootScope.applyOptionsConfirm().then ->
$window.location.reload()
$scope.resetOptionsSync = ->
omegaTarget.resetOptionsSync().then ->
if !$scope.gistId or !$scope.gistToken
$rootScope.showAlert(
type: 'error'
message: 'Gist Id or Gist Token is required'
)
return
omegaTarget.resetOptionsSync({
gistId: $scope.gistId
gistToken: $scope.gistToken
}).then( ->
$rootScope.applyOptionsConfirm().then ->
$window.location.reload()
).catch((e) ->
$rootScope.showAlert(
type: 'error'
message: e + ''
)
console.log('error:::', e)
)

View File

@ -90,9 +90,6 @@ angular.module('omega').controller 'MasterCtrl', ($scope, $rootScope, $window,
plainOptions = angular.fromJson(angular.toJson($rootScope.options))
patch = diff.diff($rootScope.optionsOld, plainOptions)
omegaTarget.optionsPatch(patch).then ->
omegaTarget.state('customCss').then (customCss = '') ->
$scope.customCss = customCss
$rootScope.showAlert(
type: 'success'
i18n: 'options_saveSuccess'

View File

@ -14,17 +14,17 @@ section
p {{'about_app_description' | tr}}
section
p
button.btn.btn-info(ng-click='reportIssue()')
button.btn.btn-info(ng-click='reportIssue()' ladda='issueReporting')
span.glyphicon.glyphicon-comment
= ' '
| {{'popup_reportIssues' | tr}}
= ' '
button.btn.btn-default(ng-click='downloadLog()')
button.btn.btn-default(ng-click='downloadLog()' ladda='logDownloading' data-spinner-color="currentColor")
span.glyphicon.glyphicon-download
= ' '
| {{'popup_errorLog' | tr}}
= ' '
button.btn.btn-danger(ng-click='showResetOptionsModal()')
button.btn.btn-danger(ng-click='showResetOptionsModal()' ladda='optionsReseting')
span.glyphicon.glyphicon-alert
= ' '
| {{'options_reset' | tr}}

View File

@ -38,15 +38,47 @@ section.settings-group
| {{'options_restoreOnlineSubmit' | tr}}
section.settings-group
h3 {{'options_group_syncing' | tr}}
div
form
div.form-group
label {{'Gist Id'}}
.input-group.width-limit
span.input-group-addon {{'ID'}}
input.form-control(type='text' ng-model='gistId' ng-readonly='syncOptions == "sync"' placeholder="Gist Id e.g. https://gist.github.com/{username}/{Gist Id}")
span.help-block
a(href="https://gist.github.com/" role="button" target="_blank")
| {{'Create a secret Gist. '}}
strong
| {{" Note: If it's a public Gist, your options can be searched by others。"}}
div.form-group
label {{'Gist Token'}}
.input-group.width-limit
span.input-group-addon {{'TOKEN'}}
input.form-control(type='text' ng-model='gistToken' ng-readonly='syncOptions == "sync"' placeholder="Gist Token")
span.help-block
a(href="https://github.com/settings/tokens/new" role="button" target="_blank")
| {{ 'Create a token that manages the Gist.'}}
div(ng-show='syncOptions == "pristine" || syncOptions == "disabled"')
p.help-block(omega-html='"options_syncPristineHelp" | tr')
p
button.btn.btn-default(ng-click='enableOptionsSync()')
button.btn.btn-default(ng-click='enableOptionsSync()' ladda='enableOptionsSyncing' data-spinner-color="currentColor")
span.glyphicon.glyphicon-cloud-upload
= ' '
| {{'options_syncEnable' | tr}}
div(ng-show='syncOptions == "sync"')
p.alert.alert-success.width-limit
button.btn.btn-sm.btn-success(ng-click='checkOptionsSyncChange()' ladda='enableOptionsSyncing')
span.glyphicon.glyphicon-refresh
span {{' last sync date: '}}
| {{ lastGistSync | date:'medium'}}
| {{'('}}
| {{lastGistState}}
| {{')'}}
a(href="https://gist.github.com/{{gistId}}" role="button" target="_blank")
| {{' '}}
span.glyphicon.glyphicon-link
br
br
span.glyphicon.glyphicon-ok
= ' '
| {{"options_syncSyncAlert" | tr}}
@ -57,13 +89,13 @@ section.settings-group
= ' '
| {{'options_syncDisable' | tr}}
div(ng-show='syncOptions == "conflict"')
p.alert.alert-info.width-limit
p.alert.alert-danger.width-limit
span.glyphicon.glyphicon-info-sign
= ' '
| {{"options_syncConflictAlert" | tr}}
p.help-block(omega-html='"options_syncConflictHelp" | tr')
p
button.btn.btn-danger(ng-click='enableOptionsSync({force: true})')
button.btn.btn-danger(ng-click='enableOptionsSync({force: true})' ladda='enableOptionsSyncing' )
span.glyphicon.glyphicon-cloud-download
= ' '
| {{'options_syncEnableForce' | tr}}