silverstripe-terraformers / silverstripe-embargo-expiry Goto Github PK
View Code? Open in Web Editor NEWLicense: BSD 3-Clause "New" or "Revised" License
License: BSD 3-Clause "New" or "Revised" License
The messages from getEmbargoExpiryFieldNoticeMessage (and others) show up twice in the CMS, as shown below. I confirmed that the method is actually called twice by making a local modification to add a static counter to the method (not shown, but easy to reproduce). However, I couldn't find where the second call is coming from.
See #12 for a potential UX solution to this issue.
<!DOCTYPE html><html><head><title>POST /admin/pages/edit/EditForm/2/</title><link rel="stylesheet" type="text/css" href="/resources/silverstripe/framework/client/styles/debug.css?m=1511148410" /></head><body><div class="header info error"><h1>[Emergency] Uncaught LogicException: The current member does not have permission to publish this ChangeSet.</h1><h3>POST /admin/pages/edit/EditForm/2/</h3><p>Line <strong>134</strong> in <strong>/var/www/mysite/www/vendor/silverstripe/versioned/src/ChangeSet.php</strong></p></div><div class="info"><h3>Source</h3><pre><span>125</span> "ChangeSet can't be published if it has been already published or reverted."
<span>126</span> );
<span>127</span> }
<span>128</span> if (!$this->isSynced()) {
<span>129</span> throw new ValidationException(
<span>130</span> "ChangeSet does not include all necessary changes and cannot be published."
<span>131</span> );
<span>132</span> }
<span>133</span> if (!$this->canPublish()) {
<span>134</span> <span class="error"> throw new LogicException("The current member does not have permission to publish this ChangeSet.");
</span><span>135</span> }
<span>136</span>
<span>137</span> DB::get_conn()->withTransaction(function () {
<span>138</span> foreach ($this->Changes() as $change) {
<span>139</span> /** @var ChangeSetItem $change */
<span>140</span> $change->publish();
</pre></div><div class="info"><h3>Trace</h3><ul><li><b>SilverStripe\Versioned\ChangeSet->publish()</b>
<br />
Versioned.php:1484</li>
<li><b>SilverStripe\Versioned\Versioned->publishRecursive()</b>
<br />
</li>
<li><b>call_user_func_array(Array, Array)</b>
<br />
Extensible.php:144</li>
<li><b>SilverStripe\View\ViewableData->SilverStripe\Core\{closure}(Page, Array)</b>
<br />
CustomMethods.php:61</li>
<li><b>SilverStripe\View\ViewableData->__call(publishRecursive, Array)</b>
<br />
CMSMain.php:1628</li>
<li><b>SilverStripe\CMS\Controllers\CMSMain->save(Array, SilverStripe\Forms\Form)</b>
<br />
CMSMain.php:1843</li>
<li><b>SilverStripe\CMS\Controllers\CMSMain->publish(Array, SilverStripe\Forms\Form, SilverStripe\Control\HTTPRequest, SilverStripe\Admin\LeftAndMainFormRequestHandler)</b>
<br />
FormRequestHandler.php:231</li>
<li><b>SilverStripe\Forms\FormRequestHandler->httpSubmission(SilverStripe\Control\HTTPRequest)</b>
<br />
RequestHandler.php:320</li>
<li><b>SilverStripe\Control\RequestHandler->handleAction(SilverStripe\Control\HTTPRequest, httpSubmission)</b>
<br />
RequestHandler.php:201</li>
<li><b>SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)</b>
<br />
RequestHandler.php:225</li>
<li><b>SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)</b>
<br />
Controller.php:207</li>
<li><b>SilverStripe\Control\Controller->handleRequest(SilverStripe\Control\HTTPRequest)</b>
<br />
LeftAndMain.php:750</li>
<li><b>SilverStripe\Admin\LeftAndMain->handleRequest(SilverStripe\Control\HTTPRequest)</b>
<br />
AdminRootController.php:123</li>
<li><b>SilverStripe\Admin\AdminRootController->handleRequest(SilverStripe\Control\HTTPRequest)</b>
<br />
Director.php:360</li>
<li><b>SilverStripe\Control\Director->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)</b>
<br />
VersionedHTTPMiddleware.php:40</li>
<li><b>SilverStripe\Versioned\VersionedHTTPMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)</b>
<br />
HTTPMiddlewareAware.php:62</li>
<li><b>SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)</b>
<br />
AuthenticationMiddleware.php:61</li>
<li><b>SilverStripe\Security\AuthenticationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)</b>
<br />
HTTPMiddlewareAware.php:62</li>
<li><b>SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)</b>
<br />
CanonicalURLMiddleware.php:155</li>
<li><b>SilverStripe\Control\Middleware\CanonicalURLMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)</b>
<br />
HTTPMiddlewareAware.php:62</li>
<li><b>SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)</b>
<br />
FlushMiddleware.php:26</li>
<li><b>SilverStripe\Control\Middleware\FlushMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)</b>
<br />
HTTPMiddlewareAware.php:62</li>
<li><b>SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)</b>
<br />
RequestProcessor.php:66</li>
<li><b>SilverStripe\Control\RequestProcessor->process(SilverStripe\Control\HTTPRequest, Closure)</b>
<br />
HTTPMiddlewareAware.php:62</li>
<li><b>SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)</b>
<br />
SessionMiddleware.php:20</li>
<li><b>SilverStripe\Control\Middleware\SessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)</b>
<br />
HTTPMiddlewareAware.php:62</li>
<li><b>SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)</b>
<br />
AllowedHostsMiddleware.php:60</li>
<li><b>SilverStripe\Control\Middleware\AllowedHostsMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)</b>
<br />
HTTPMiddlewareAware.php:62</li>
<li><b>SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)</b>
<br />
TrustedProxyMiddleware.php:176</li>
<li><b>SilverStripe\Control\Middleware\TrustedProxyMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)</b>
<br />
HTTPMiddlewareAware.php:62</li>
<li><b>SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)</b>
<br />
HTTPMiddlewareAware.php:65</li>
<li><b>SilverStripe\Control\Director->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)</b>
<br />
Director.php:369</li>
<li><b>SilverStripe\Control\Director->handleRequest(SilverStripe\Control\HTTPRequest)</b>
<br />
HTTPApplication.php:48</li>
<li><b>SilverStripe\Control\HTTPApplication->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)</b>
<br />
</li>
<li><b>call_user_func(Closure, SilverStripe\Control\HTTPRequest)</b>
<br />
HTTPApplication.php:66</li>
<li><b>SilverStripe\Control\HTTPApplication->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)</b>
<br />
</li>
<li><b>call_user_func(Closure, SilverStripe\Control\HTTPRequest)</b>
<br />
ErrorControlChainMiddleware.php:56</li>
<li><b>SilverStripe\Core\Startup\ErrorControlChainMiddleware->SilverStripe\Core\Startup\{closure}(SilverStripe\Core\Startup\ErrorControlChain)</b>
<br />
</li>
<li><b>call_user_func(Closure, SilverStripe\Core\Startup\ErrorControlChain)</b>
<br />
ErrorControlChain.php:236</li>
<li><b>SilverStripe\Core\Startup\ErrorControlChain->step()</b>
<br />
ErrorControlChain.php:226</li>
<li><b>SilverStripe\Core\Startup\ErrorControlChain->execute()</b>
<br />
ErrorControlChainMiddleware.php:69</li>
<li><b>SilverStripe\Core\Startup\ErrorControlChainMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)</b>
<br />
HTTPMiddlewareAware.php:62</li>
<li><b>SilverStripe\Control\HTTPApplication->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)</b>
<br />
HTTPMiddlewareAware.php:65</li>
<li><b>SilverStripe\Control\HTTPApplication->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)</b>
<br />
HTTPApplication.php:67</li>
<li><b>SilverStripe\Control\HTTPApplication->execute(SilverStripe\Control\HTTPRequest, Closure, )</b>
<br />
HTTPApplication.php:49</li>
<li><b>SilverStripe\Control\HTTPApplication->handle(SilverStripe\Control\HTTPRequest)</b>
<br />
index.php:17</li>
</ul></div></body></html>
To replicate:
My\Namespace\DataObject:
extensions:
- SilverStripe\Versioned\Versioned
- Terraformers\EmbargoExpiry\Extension\EmbargoExpiryExtension
I have seen the following error thrown in console but can't reliably recreate it but assume it's related:
embargo-expiry.js?m=1625172243:1 Uncaught TypeError: Cannot read properties of undefined (reading 'entwine')
at Object../client/src/bundles/embargo-expiry.js (embargo-expiry.js?m=1625172243:1)
at t (embargo-expiry.js?m=1625172243:1)
at embargo-expiry.js?m=1625172243:1
at embargo-expiry.js?m=1625172243:1
Abandoned:
https://github.com/jeremeamia/super_closure
Replacement:
https://github.com/opis/closure
Or find a new way to support Fluent while keeping BC intact.
Could you please add a version tag/release for this module? It makes using it in projects safer as general users (like myself) don't have to worry about new commits to master potentially breaking user code.
Need to implement our own Form actions so that we can have different criteria in it's performReadonlyTransformation()
method.
It's a bit weird to have input field for a scheduled publish, which could then be rendered ineffective by immediately publishing the page anyway (rather than just saving the draft). Rather than conditionally removing the "publish" option when unsaved scheduled publish input is present, use an inline button to set it. This should refresh the form, since it changes the available actions to "remove embargo".
At the moment, you can add the DataExtension
to objects other than SiteTree
already, from what I can tell there's no assumptions built in. But: Since the actions are added via a CMSMain
extension, they won't be available where those records are edited. We should configure ModelAdmin
to add these actions by default when the extension is detected, and provide instructions how to add it to your own GridField
instantiations.
See extendedRequiredFieldEmbargoExpiry function in advancedworkflow module.
Find any unpublished page that doesn’t have embargo or expiry set. Note the style of the Publish button - green with rocket.
Enter embargo date - Publish button disappears.
Clear embargo date. Note the style of the Publish button - it should be the ‘unpublished’ green version with rocket, but it is transparent and ticked, the same styling as for a published page.
At the moment, it looks like an author got up at 3am or something to publish a page. The only indication for an automated process is that the scheduled dates are removed in the diff with the previous version.
Currently have 0% coverage on this extension.
Travis no longer give out monthly reoccurring allowances for open source projects. Need to switch to another service.
Just need to find some time to put what's in my head down into the docs.
Oftentimes, embargoes are for a temporary new content state, e.g. promotion of a deal on the homepage until it expires. The expiry would be around this particular content version, and shouldn't unpublish the homepage when the expiry date hits. Instead, it should just restore the old version of the homepage.
There's two schools of thought:
At the moment, this information is hidden away in a separate tab. If you allow editing for scheduled pages, authors might accidentally publish the page, effectively canceling the schedule. If you deny editing for scheduled pages, authors will get confused why they can't edit this page.
A few options:
Actions:
The Actions menu bar might start getting a bit full.
Hello,
First of all thanks for the module.
I used the SS3 version of this module multiple times without a problem.
I seem to have one with this one.
I installed the module and applied the extension to SiteTree, the interface works fine: I can schedule publish and unpublish and clear them from the tab in page view.
Problem comes when the scheduled job tries to execute from my Job queue: I always get a broken job state with the job spitting this out:
[2019-08-07 16:06:01][INFO] Job caused exception Object->__call(): the method 'setIsUnPublishJobRunning' does not exist on 'StandardInnerPage' in /var/www/isf/githtdocs/vendor/silverstripe/framework/src/Core/CustomMethods.php at line 54
[2019-08-07 16:06:01][INFO] ERROR [Emergency]: Uncaught BadMethodCallException: Object->__call(): the method 'setIsUnPublishJobRunning' does not exist on 'StandardInnerPage'
IN GET dev/tasks/ProcessJobQueueTask
Line 54 in /var/www/isf/githtdocs/vendor/silverstripe/framework/src/Core/CustomMethods.php
Source
======
45: * @throws BadMethodCallException
46: */
47: public function __call($method, $arguments)
48: {
49: // If the method cache was cleared by an an Object::add_extension() /
Object::remove_extension()
50: // call, then we should rebuild it.
51: $class = static::class;
52: $config = $this->getExtraMethodConfig($method);
53: if (empty($config)) {
* 54: throw new BadMethodCallException(
55: "Object->__call(): the method '$method' does not exist on '$class'"
56: );
57: }
58:
59: switch (true) {
60: case isset($config['callback']): {
Trace
=====
SilverStripe\View\ViewableData->__call(setIsUnPublishJobRunning, Array)
UnPublishTargetJob.php:91
Terraformers\EmbargoExpiry\Job\UnPublishTargetJob->process()
QueuedJobService.php:784
Symbiote\QueuedJobs\Services\QueuedJobService->runJob(26)
QueuedJobService.php:1186
Symbiote\QueuedJobs\Services\QueuedJobService->processJobQueue(2)
QueueRunner.php:23
Symbiote\QueuedJobs\Tasks\Engines\QueueRunner->runQueue(2)
QueuedJobService.php:1163
Symbiote\QueuedJobs\Services\QueuedJobService->runQueue(2)
ProcessJobQueueTask.php:59
Symbiote\QueuedJobs\Tasks\ProcessJobQueueTask->run(SilverStripe\Control\HTTPRequest)
TaskRunner.php:104
SilverStripe\Dev\TaskRunner->runTask(SilverStripe\Control\HTTPRequest)
RequestHandler.php:320
SilverStripe\Control\RequestHandler->handleAction(SilverStripe\Control\HTTPRequest, runTask)
Controller.php:284
SilverStripe\Control\Controller->handleAction(SilverStripe\Control\HTTPRequest, runTask)
RequestHandler.php:202
SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
Controller.php:212
SilverStripe\Control\Controller->handleRequest(SilverStripe\Control\HTTPRequest)
RequestHandler.php:226
SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
Controller.php:212
SilverStripe\Control\Controller->handleRequest(SilverStripe\Control\HTTPRequest)
Director.php:361
SilverStripe\Control\Director->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
VersionedHTTPMiddleware.php:41
SilverStripe\Versioned\VersionedHTTPMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
BasicAuthMiddleware.php:68
SilverStripe\Security\BasicAuthMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
AuthenticationMiddleware.php:61
SilverStripe\Security\AuthenticationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
CanonicalURLMiddleware.php:188
SilverStripe\Control\Middleware\CanonicalURLMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
HTTPCacheControlMiddleware.php:42
SilverStripe\Control\Middleware\HTTPCacheControlMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
ChangeDetectionMiddleware.php:27
SilverStripe\Control\Middleware\ChangeDetectionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
FlushMiddleware.php:29
SilverStripe\Control\Middleware\FlushMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
RequestProcessor.php:66
SilverStripe\Control\RequestProcessor->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
SessionMiddleware.php:20
SilverStripe\Control\Middleware\SessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
AllowedHostsMiddleware.php:60
SilverStripe\Control\Middleware\AllowedHostsMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
TrustedProxyMiddleware.php:176
SilverStripe\Control\Middleware\TrustedProxyMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
HTTPMiddlewareAware.php:65
SilverStripe\Control\Director->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
Director.php:370
SilverStripe\Control\Director->handleRequest(SilverStripe\Control\HTTPRequest)
HTTPApplication.php:48
SilverStripe\Control\HTTPApplication->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
call_user_func(Closure, SilverStripe\Control\HTTPRequest)
HTTPApplication.php:66
SilverStripe\Control\HTTPApplication->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
HTTPMiddlewareAware.php:65
SilverStripe\Control\HTTPApplication->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
HTTPApplication.php:67
SilverStripe\Control\HTTPApplication->execute(SilverStripe\Control\HTTPRequest, Closure, )
HTTPApplication.php:49
SilverStripe\Control\HTTPApplication->handle(SilverStripe\Control\HTTPRequest)
cli-script.php:22
In this case my 'StandardInnerPage' is just my custom page type. I am positive the extension is applied because I'm able to add the scheduled publish / unpublish from the page view and also because I can run the job manually from the job queue without an error (correctly publishes unpublishes the page).
It sort of looks like the extension is not applied when the job runs from the ProcessJobQueueTask, however I have absolutely no idea why this happens. My other scheduled jobs are running fine.
Any insight on this or direction you can point me to?
I'm using version 1.0.1
Thanks
If your site has a SiteTree extension extending canEdit and returning true then the default SiteTree Permissions are never called, and the user is always granted permission.
Background:
SiteTree::canEdit() currently doesn't check the normal SiteTree edit permissions if any extension decides on whether the user can edit the page or not. https://github.com/silverstripe/silverstripe-cms/blob/4/code/Model/SiteTree.php#L1279
It supports three values for extensions implementing canEdit: true
, false
and null
. In the current format, true
basically means "ignore the normal SiteTree permission scheme”, which I don’t think is the intention, general permission practice is block until allowed.
DataObject::extendedCan() processes the results from all extensions of which null results are filtered out and the lowest of true and false values (always false unless all are true) is returned. This means if an extension returns true, and no other extension returns false, then it automatically overrides the default permissions. https://github.com/silverstripe/silverstripe-framework/blob/4/src/ORM/DataObject.php#L2782
Impact:
Users could have edit permissions because default and inherited permissions are only checked after extensions are processed, and extensions might return true (give permissions regardless) instead of null (don’t affect the outcome). If a user was able to login to the system, they could edit any page they had if all extensions applied returned true.
I have specified this in the project's mysite.yml
Symbiote\QueuedJobs\Controllers\QueuedJobsAdmin:
menu_title: 'CMS Queued Tasks'
Yet I still see it display as Jobs
This is on a SS4 site. Thanks in advance.
You should be able to add an expiry date for content even if it is currently under embargo.
So I created a page (ReginonPage extens Page) with couple of Elemental blocks (https://github.com/dnadesign/silverstripe-elemental), saved and published. Then made more changes into the block and set Desired publish date/time
for few hours later. Once I log out from the CMS, all of the blocks disappeared from the page which you can see below:
Then I logged in to the admin
section and reloaded the page again. This time the page blocks shown as before! This trend happens when I'm logged out of CMS until it publishes the changes.
"version": "1.0.3"
Need to validate that the Immediate queue still checks for "start after" date.
This module essentially duplicates a subset of the features in https://github.com/symbiote/silverstripe-advancedworkflow/, without coupling them to workflow concerns. Ideally we wouldn't need this duplication - the module maintainer Marcus agrees that we should split out the feature as well.
Missing features in new module (or bridge to advancedworkflow):
Existing discussions:
symbiote/silverstripe-advancedworkflow#47
symbiote/silverstripe-advancedworkflow#187
It's all just broken.
It probably does, and it shouldn't.
Since PublishOnDate
and UnpublishOnDate
are part of the record, any revert to an earlier version of this record with the columns populated will trigger a publish or unpublished job. In case the dates are in the past, it leads to an immediate action - which will be very confusing to authors.
Add PHP 7.1 flavour.
Add PHP 7.1 requirement.
Apart from requiring publish permissions to perform these actions, there might be reasons within organisations to allow publish, but not scheduling of the same actions. I can't actually think of a practical example where this might apply though, publish and scheduling are fairly similar in terms of organisational roles. Maybe it's a non-issue?
Allow an author to set a date in the preview panel and browse through the site at that state, including any embargo or expire actions applied
We've attempted this before with the SiteTreeFutureState extension in the old silverstripe-workflow
module.
It has been decided that Core will not support (non-core) status flags with text displayed (they will currently overlap). Instead, it is suggested that you use the new "badge" system.
We should remove our status flag text
values, and add badges.
Add/update README.
Add recommendation of "Date/time picker" module:
https://github.com/praxisnetau/silverware-calendar
At the moment, an author without publishing permissions can circumvent that restriction by scheduling a publish, which is a bit of a security issue.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.